/* ==================================================================================
BlueCV Pro — 旗舰级简历系统(豪华升级版)
1) 模板库:34 款(22 款经典旗舰全部保留并豪华升级 + 12 款全新甄选豪华升级)
2) 版式引擎:side / top / classic + 全新 hero / stripe / split / magazine / frame
3) 导出引擎:真实 OOXML(.docx / .xlsx),样式、内容、版面与预览严格一致
并保留原有 html2canvas 图片快照导出作为「图片快照」模式
4) 豪华升级:渐变深度、层叠阴影、精装装饰、黄金比例字距、双色系点缀
================================================================================== */
// ==================== 旗舰级模板配置库(豪华升级版) ====================
// 配置属性:layout, primary(主色), bg(背景色), text(主字色), font, secStyle, skillStyle, itemStyle, accent
// 新增豪华属性:accent2(二级点缀色), titleDeco(标题装饰), cardRadius(卡片圆角), premium(豪华阴影)
const templates = [
/* ---------- 经典旗舰 22 款(完整保留 + 豪华升级) ---------- */
{ id: 't1', group:'经典旗舰', name: '卓越总监 (藏青鎏金)', layout: 'side', nameInSide: true, bg: '#ffffff', text: '#1e293b', primary: '#0c1e3d', sideBg: 'linear-gradient(180deg, #0c1e3d 0%, #16284a 50%, #0c1e3d 100%)', sideText: '#f8fafc', font: 'Georgia, serif', secStyle: 'left-border', skillStyle: 'bar', itemStyle: 'dot-line', accent: '#c9a227', accent2: '#e6c558', titleDeco: 'gold-bar', cardRadius: 12, premium: true },
{ id: 't2', group:'经典旗舰', name: '金融领袖 (铂金商务)', layout: 'top', topBg: 'linear-gradient(135deg, #0a1854 0%, #1e3a8a 50%, #2553a8 100%)', topText: '#ffffff', bg: '#ffffff', text: '#1e293b', primary: '#0a1854', font: 'sans-serif', secStyle: 'underline', skillStyle: 'tag', itemStyle: 'simple', accent: '#c0c9d4', accent2: '#e8edf3', titleDeco: 'silver-line', cardRadius: 10, premium: true },
{ id: 't3', group:'经典旗舰', name: '投行精英 (极简雅致)', layout: 'classic', headerAlign: 'left', headerBorder: 'none', bg: '#ffffff', text: '#334155', primary: '#1a3a6b', font: 'sans-serif', secStyle: 'center-line', skillStyle: 'text', itemStyle: 'simple', accent: '#2a5caa', accent2: '#7ba3d9', titleDeco: 'thin-underline', cardRadius: 8, premium: false },
{ id: 't4', group:'经典旗舰', name: '科技CFO (暗黑未来)', layout: 'side', nameInSide: true, bg: '#030712', text: '#cbd5e1', primary: '#1e40af', sideBg: 'linear-gradient(180deg, #030712 0%, #0f172a 40%, #030712 100%)', sideText: '#f1f5f9', font: 'sans-serif', secStyle: 'background', skillStyle: 'dot', itemStyle: 'card', accent: '#3b82f6', accent2: '#60a5fa', titleDeco: 'glow-line', cardRadius: 14, premium: true },
{ id: 't5', group:'经典旗舰', name: '跨国高管 (海外风尚)', layout: 'top', topBg: 'linear-gradient(135deg, #0c2461 0%, #1e40af 40%, #2563eb 70%, #3b82f6 100%)', topText: '#ffffff', topRadius: '0 0 48px 0', bg: '#ffffff', text: '#1e293b', primary: '#0c2461', font: 'sans-serif', secStyle: 'left-border', skillStyle: 'tag', itemStyle: 'card', accent: '#d4a017', accent2: '#f0c445', titleDeco: 'gold-accent', cardRadius: 12, premium: true },
{ id: 't6', group:'经典旗舰', name: '权势行政 (右侧金蓝)', layout: 'side', sideRight: true, nameInSide: false, bg: '#f8fafc', text: '#1e293b', primary: '#0f1c3f', sideBg: 'linear-gradient(180deg, #e2e8f0 0%, #cbd5e1 50%, #e2e8f0 100%)', sideText: '#0f1c3f', font: 'Georgia, serif', secStyle: 'underline', skillStyle: 'bar', itemStyle: 'simple', accent: '#1a3a6b', accent2: '#c9a227', titleDeco: 'double-line', cardRadius: 10, premium: false },
{ id: 't7', group:'经典旗舰', name: '现代咨询 (几何框线)', layout: 'classic', headerAlign: 'center', headerBorder: '3px solid #1d4ed8', bg: '#ffffff', text: '#1e293b', primary: '#1d4ed8', font: 'sans-serif', secStyle: 'background', skillStyle: 'tag', itemStyle: 'card', accent: '#2563eb', accent2: '#60a5fa', titleDeco: 'boxed', cardRadius: 10, premium: true },
{ id: 't8', group:'经典旗舰', name: '稳如泰山 (厚重底边)', layout: 'top', topBg: 'linear-gradient(135deg, #0a1854 0%, #1e3a8a 100%)', topText: '#ffffff', topBorderBottom: '6px solid #d4a017', bg: '#ffffff', text: '#1e293b', primary: '#0a1854', font: 'sans-serif', secStyle: 'left-border', skillStyle: 'bar', itemStyle: 'simple', accent: '#d4a017', accent2: '#f0c445', titleDeco: 'gold-base', cardRadius: 8, premium: true },
{ id: 't9', group:'经典旗舰', name: '学术权威 (衬线经典)', layout: 'classic', headerAlign: 'center', headerBorder: '2px solid #475569', bg: '#ffffff', text: '#0f172a', primary: '#0f172a', font: 'Times New Roman, serif', secStyle: 'center-line', skillStyle: 'text', itemStyle: 'simple', accent: '#334155', accent2: '#64748b', titleDeco: 'serif-double', cardRadius: 6, premium: false },
{ id: 't10', group:'经典旗舰', name: '合伙人级 (浅蓝侧栏)', layout: 'side', nameInSide: false, bg: '#ffffff', text: '#1e293b', primary: '#075985', sideBg: 'linear-gradient(180deg, #f0f9ff 0%, #e0f2fe 50%, #f0f9ff 100%)', sideText: '#082f49', font: 'sans-serif', secStyle: 'underline', skillStyle: 'bar', itemStyle: 'dot-line', accent: '#0284c7', accent2: '#38bdf8', titleDeco: 'ocean-line', cardRadius: 10, premium: true },
{ id: 't11', group:'经典旗舰', name: '硬核极客 (代码终端)', layout: 'top', topBg: 'linear-gradient(135deg, #020617 0%, #0f172a 100%)', topText: '#38bdf8', bg: '#ffffff', text: '#1e293b', primary: '#0369a1', font: 'monospace', secStyle: 'left-border', skillStyle: 'tag', itemStyle: 'simple', accent: '#0ea5e9', accent2: '#7dd3fc', titleDeco: 'terminal', cardRadius: 8, premium: false },
{ id: 't12', group:'经典旗舰', name: '优雅律动 (渐变侧栏)', layout: 'side', nameInSide: true, bg: '#ffffff', text: '#1e293b', primary: '#1e3a8a', sideBg: 'linear-gradient(180deg, #0c1e3d 0%, #1e3a8a 40%, #3b82f6 80%, #60a5fa 100%)', sideText: '#ffffff', font: 'sans-serif', secStyle: 'underline', skillStyle: 'dot', itemStyle: 'simple', accent: '#93c5fd', accent2: '#dbeafe', titleDeco: 'gradient-flow', cardRadius: 10, premium: true },
{ id: 't13', group:'经典旗舰', name: '气度非凡 (居中大气)', layout: 'classic', headerAlign: 'center', headerBorder: 'none', bg: '#ffffff', text: '#0c1e3d', primary: '#0c1e3d', font: 'Georgia, serif', secStyle: 'underline', skillStyle: 'tag', itemStyle: 'dot-line', accent: '#c9a227', accent2: '#e6c558', titleDeco: 'gold-center', cardRadius: 10, premium: true },
{ id: 't14', group:'经典旗舰', name: '大道至简 (无框极简)', layout: 'classic', headerAlign: 'left', headerBorder: '1px dashed #94a3b8', bg: '#fefefe', text: '#334155', primary: '#1e40af', font: 'sans-serif', secStyle: 'none', skillStyle: 'text', itemStyle: 'simple', accent: '#3b82f6', accent2: '#93c5fd', titleDeco: 'minimal', cardRadius: 6, premium: false },
{ id: 't15', group:'经典旗舰', name: '锐意进取 (异形切割)', layout: 'top', topBg: 'linear-gradient(135deg, #1d4ed8 0%, #2563eb 100%)', topText: '#ffffff', topClip: 'polygon(0 0, 100% 0, 100% 82%, 0 100%)', bg: '#ffffff', text: '#1e293b', primary: '#1d4ed8', font: 'sans-serif', secStyle: 'left-border', skillStyle: 'bar', itemStyle: 'card', accent: '#60a5fa', accent2: '#93c5fd', titleDeco: 'clip-angle', cardRadius: 12, premium: true },
{ id: 't16', group:'经典旗舰', name: '经典流线 (细腻线条)', layout: 'side', nameInSide: true, bg: '#ffffff', text: '#1e293b', primary: '#0369a1', sideBg: '#ffffff', sideText: '#0f172a', sideBorder: '1px solid #e2e8f0', font: 'sans-serif', secStyle: 'underline', skillStyle: 'bar', itemStyle: 'dot-line', accent: '#0284c7', accent2: '#38bdf8', titleDeco: 'hair-line', cardRadius: 8, premium: false },
{ id: 't17', group:'经典旗舰', name: '质感暗夜 (午夜幽蓝)', layout: 'classic', headerAlign: 'center', headerBorder: '2px solid #38bdf8', bg: '#0c0f1a', text: '#f8fafc', primary: '#38bdf8', font: 'sans-serif', secStyle: 'center-line', skillStyle: 'dot', itemStyle: 'card', accent: '#7dd3fc', accent2: '#bae6fd', titleDeco: 'neon-glow', cardRadius: 14, premium: true },
{ id: 't18', group:'经典旗舰', name: '极速青蓝 (交汇渐变)', layout: 'top', topBg: 'linear-gradient(90deg, #0e7490 0%, #06b6d4 30%, #3b82f6 70%, #1d4ed8 100%)', topText: '#ffffff', bg: '#ffffff', text: '#1e293b', primary: '#0e7490', font: 'sans-serif', secStyle: 'background', skillStyle: 'tag', itemStyle: 'simple', accent: '#0ea5e9', accent2: '#67e8f9', titleDeco: 'wave-gradient', cardRadius: 10, premium: true },
{ id: 't19', group:'经典旗舰', name: '领航时代 (沉稳粗边)', layout: 'classic', headerAlign: 'left', headerBorder: '5px solid #0a1854', bg: '#ffffff', text: '#1e293b', primary: '#0a1854', font: 'sans-serif', secStyle: 'background', skillStyle: 'text', itemStyle: 'dot-line', accent: '#1d4ed8', accent2: '#3b82f6', titleDeco: 'thick-bar', cardRadius: 8, premium: true },
{ id: 't20', group:'经典旗舰', name: '纯粹本质 (蓝点点缀)', layout: 'classic', headerAlign: 'center', headerBorder: 'none', bg: '#ffffff', text: '#1e293b', primary: '#1e40af', font: 'sans-serif', secStyle: 'left-border', skillStyle: 'dot', itemStyle: 'simple', accent: '#3b82f6', accent2: '#60a5fa', titleDeco: 'dot-accent', cardRadius: 6, premium: false },
{ id: 't21', group:'经典旗舰', name: '璀璨星河 (星空暗纹)', layout: 'side', nameInSide: true, bg: '#ffffff', text: '#1e293b', primary: '#0c0f1a', sideBg: 'radial-gradient(circle at 70% 20%, #1e3a8a 0%, #0c0f1a 40%, #020617 100%)', sideText: '#e0e7ff', font: 'sans-serif', secStyle: 'underline', skillStyle: 'tag', itemStyle: 'dot-line', accent: '#fbbf24', accent2: '#fde68a', titleDeco: 'star-gold', cardRadius: 12, premium: true },
{ id: 't22', group:'经典旗舰', name: '深海巨鲸 (睿智深渊)', layout: 'top', topBg: 'linear-gradient(135deg, #020617 0%, #0a192f 50%, #0d2137 100%)', topText: '#64ffda', bg: '#ffffff', text: '#0a192f', primary: '#0056b3', font: 'sans-serif', secStyle: 'underline', skillStyle: 'bar', itemStyle: 'simple', accent: '#64ffda', accent2: '#a7f3d0', titleDeco: 'deep-sea', cardRadius: 10, premium: true },
/* ---------- 全新甄选 12 款(豪华升级,含 5 种全新版式引擎) ---------- */
{ id: 't23', group:'全新甄选', name: '尊享黑金 (中央礼帽)', layout: 'hero', topBg: 'linear-gradient(135deg, #030712 0%, #111827 40%, #1f2937 100%)', topText: '#ffffff', topBorderBottom: '4px solid #c9a227', bg: '#fffdf8', text: '#1f2937', primary: '#111827', accent: '#c9a227', accent2: '#e6c558', font: 'Georgia, serif', secStyle: 'center-line', skillStyle: 'bar', itemStyle: 'simple', titleDeco: 'royal-gold', cardRadius: 10, premium: true },
{ id: 't24', group:'全新甄选', name: '晨曦蓝调 (左侧色带)', layout: 'stripe', stripeBg: 'linear-gradient(180deg, #1e3a8a 0%, #2563eb 40%, #3b82f6 70%, #38bdf8 100%)', stripeWidth: 22, bg: '#ffffff', text: '#1e293b', primary: '#1e3a8a', accent: '#0ea5e9', accent2: '#7dd3fc', font: 'sans-serif', secStyle: 'left-border', skillStyle: 'bar', itemStyle: 'dot-line', titleDeco: 'stripe-flow', cardRadius: 10, premium: true },
{ id: 't25', group:'全新甄选', name: '双生对称 (分栏头图)', layout: 'split', splitLeftBg: 'linear-gradient(135deg, #0a1f4c 0%, #0f2c5c 100%)', splitLeftText: '#ffffff', splitRightBg: 'linear-gradient(135deg, #eef2f7 0%, #f8fafc 100%)', splitRightText: '#0f2c5c', bg: '#ffffff', text: '#1e293b', primary: '#0f2c5c', accent: '#2f6fed', accent2: '#5b8def', font: 'sans-serif', secStyle: 'underline', skillStyle: 'tag', itemStyle: 'simple', titleDeco: 'dual-tone', cardRadius: 10, premium: true },
{ id: 't26', group:'全新甄选', name: '杂志风范 (左栏叙事)', layout: 'magazine', topBg: 'linear-gradient(90deg, #0a1854 0%, #1e3a8a 100%)', topText: '#ffffff', sideBg: 'linear-gradient(180deg, #f8fafc 0%, #f1f5f9 50%, #f8fafc 100%)', sideText: '#0f172a', bg: '#ffffff', text: '#1e293b', primary: '#0a1854', accent: '#3b82f6', accent2: '#60a5fa', font: 'sans-serif', secStyle: 'left-border', skillStyle: 'bar', itemStyle: 'simple', titleDeco: 'editorial', cardRadius: 8, premium: true },
{ id: 't27', group:'全新甄选', name: '典藏边框 (雅致内框)', layout: 'frame', frameBorder: '3px solid #14385f', bg: '#fffdf5', text: '#26313d', primary: '#14385f', accent: '#b8860b', accent2: '#daa520', font: 'Georgia, serif', secStyle: 'center-line', skillStyle: 'text', itemStyle: 'simple', titleDeco: 'museum-frame', cardRadius: 6, premium: true },
{ id: 't28', group:'全新甄选', name: '青瓷雅韵 (青灰质感)', layout: 'side', nameInSide: true, bg: '#ffffff', text: '#25343b', primary: '#1a4754', sideBg: 'linear-gradient(180deg, #1a4754 0%, #20505c 50%, #1a4754 100%)', sideText: '#e8f4f6', font: 'sans-serif', secStyle: 'underline', skillStyle: 'dot', itemStyle: 'dot-line', accent: '#5eb1bf', accent2: '#8fd4de', titleDeco: 'celadon', cardRadius: 10, premium: true },
{ id: 't29', group:'全新甄选', name: '曜石银灰 (冷峻高级)', layout: 'top', topBg: 'linear-gradient(135deg, #1c2428 0%, #37474f 50%, #455a64 100%)', topText: '#eceff1', bg: '#ffffff', text: '#263238', primary: '#37474f', accent: '#90a4ae', accent2: '#cfd8dc', font: 'sans-serif', secStyle: 'background', skillStyle: 'bar', itemStyle: 'card', titleDeco: 'graphite', cardRadius: 12, premium: true },
{ id: 't30', group:'全新甄选', name: '朱砂点睛 (沉稳红金)', layout: 'classic', headerAlign: 'left', headerBorder: '4px solid #8c1c13', bg: '#fffafa', text: '#2b2b2b', primary: '#8c1c13', accent: '#c8963e', accent2: '#e0b860', font: 'Georgia, serif', secStyle: 'left-border', skillStyle: 'tag', itemStyle: 'simple', titleDeco: 'vermilion', cardRadius: 8, premium: true },
{ id: 't31', group:'全新甄选', name: '翡翠资本 (墨绿沉稳)', layout: 'hero', topBg: 'linear-gradient(135deg, #022c22 0%, #064e3b 40%, #065f46 80%, #047857 100%)', topText: '#ecfdf5', bg: '#ffffff', text: '#1f2937', primary: '#065f46', accent: '#10b981', accent2: '#34d399', font: 'sans-serif', secStyle: 'underline', skillStyle: 'bar', itemStyle: 'card', titleDeco: 'emerald', cardRadius: 12, premium: true },
{ id: 't32', group:'全新甄选', name: '钛金锋芒 (石墨渐层)', layout: 'magazine', topBg: 'linear-gradient(90deg, #030712 0%, #1f2937 50%, #374151 100%)', topText: '#f9fafb', sideBg: 'linear-gradient(180deg, #030712 0%, #111827 50%, #1f2937 100%)', sideText: '#e5e7eb', bg: '#ffffff', text: '#1f2937', primary: '#111827', accent: '#f59e0b', accent2: '#fbbf24', font: 'sans-serif', secStyle: 'background', skillStyle: 'dot', itemStyle: 'simple', titleDeco: 'titanium', cardRadius: 10, premium: true },
{ id: 't33', group:'全新甄选', name: '云端简约 (轻盈留白)', layout: 'stripe', stripeBg: 'linear-gradient(180deg, #cbd5e1 0%, #e2e8f0 50%, #f1f5f9 100%)', stripeWidth: 16, bg: '#ffffff', text: '#334155', primary: '#0f172a', accent: '#64748b', accent2: '#94a3b8', font: 'sans-serif', secStyle: 'none', skillStyle: 'text', itemStyle: 'simple', titleDeco: 'cloud-minimal', cardRadius: 6, premium: false },
{ id: 't34', group:'全新甄选', name: '皇家紫韵 (深紫典藏)', layout: 'split', splitLeftBg: 'linear-gradient(135deg, #2e1065 0%, #3b0764 50%, #4c1d95 100%)', splitLeftText: '#f5f3ff', splitRightBg: 'linear-gradient(135deg, #f5f3ff 0%, #ede9fe 100%)', splitRightText: '#3b0764', bg: '#ffffff', text: '#2e1065', primary: '#5b21b6', accent: '#a78bfa', accent2: '#c4b5fd', font: 'sans-serif', secStyle: 'left-border', skillStyle: 'bar', itemStyle: 'card', titleDeco: 'royal-purple', cardRadius: 12, premium: true }
];
// ==================== 颜色工具(预览与导出共用) ====================
function hex6(v, fb) {
fb = fb || 'FFFFFF';
if (!v) return fb;
var s = String(v).trim();
var found = s.match(/#([0-9a-fA-F]{3,8})/g);
if (found && found.length) {
if (found.length === 1) return normHex(found[0]);
return mixHex(normHex(found[0]), normHex(found[found.length - 1]), 0.5); // 渐变取中间色
}
var named = { white:'FFFFFF', black:'000000', transparent: fb, none: fb };
if (named[s.toLowerCase()]) return named[s.toLowerCase()];
var rgb = s.match(/rgba?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)/i);
if (rgb) return [1,2,3].map(function(i){ return ('0'+parseInt(rgb[i],10).toString(16)).slice(-2); }).join('').toUpperCase();
return fb;
}
function normHex(h) {
h = String(h).replace('#', '');
if (h.length === 3) h = h.split('').map(function(x){ return x + x; }).join('');
if (h.length > 6) h = h.slice(0, 6);
while (h.length < 6) h += '0';
return h.toUpperCase();
}
function mixHex(a, b, t) {
a = normHex(a); b = normHex(b);
var out = '';
for (var i = 0; i < 3; i++) {
var av = parseInt(a.substr(i * 2, 2), 16), bv = parseInt(b.substr(i * 2, 2), 16);
var v = Math.round(av + (bv - av) * t);
out += ('0' + Math.max(0, Math.min(255, v)).toString(16)).slice(-2);
}
return out.toUpperCase();
}
/** fg 以 alpha 透明度压在 bg 上的等效实色 */
function fadeHex(fg, bg, alpha) { return mixHex(hex6(bg, 'FFFFFF'), hex6(fg, '000000'), alpha); }
function lum(h) {
h = normHex(h);
var r = parseInt(h.substr(0,2),16)/255, g = parseInt(h.substr(2,2),16)/255, b = parseInt(h.substr(4,2),16)/255;
return 0.2126*r + 0.7152*g + 0.0722*b;
}
function bestOn(h) { return lum(h) > 0.58 ? '111827' : 'FFFFFF'; }
/** 若 accent 与背景对比过低则退回 fallback,避免"深色底 + 深色字"不可见 */
function safeAccent(accent, bg, fallback) {
var a = hex6(accent, fallback), b = hex6(bg, 'FFFFFF'), f = hex6(fallback, '000000');
return Math.abs(lum(a) - lum(b)) < 0.20 ? f : a;
}
/** 渐变背景的安全纯色提取(用于深色背景上文字对比度计算) */
function gradientBase(raw, fb) {
if (!raw) return fb || 'FFFFFF';
var s = String(raw);
var found = s.match(/#[0-9a-fA-F]{3,8}/g);
if (found && found.length) return normHex(found[0]);
return hex6(s, fb);
}
// ==================== 数据状态 (默认:财务总监 CFO) ====================
let state = { basic: {}, summary: '', education: [], work: [], projects: [], skills: [] };
let activeTab = 'basic';
let currentTpl = 't1';
let exportMode = 'native';
function tpl() { return templates.find(function(t){ return t.id === currentTpl; }) || templates[0]; }
function toast(msg, ms) {
var el = document.getElementById('toast');
el.textContent = msg; el.classList.add('show');
clearTimeout(el._t); el._t = setTimeout(function(){ el.classList.remove('show'); }, ms || 2600);
}
// 初始化函数
function init() {
const select = document.getElementById('tplSelect');
const groups = {};
templates.forEach(function(t){ (groups[t.group || '模板'] = groups[t.group || '模板'] || []).push(t); });
select.innerHTML = Object.keys(groups).map(function(g){
return '';
}).join('');
select.value = currentTpl;
select.onchange = (e) => { currentTpl = e.target.value; updatePreview(); };
const em = document.getElementById('exportMode');
em.value = exportMode;
em.onchange = (e) => { exportMode = e.target.value; };
document.querySelectorAll('.tab').forEach(btn => {
btn.onclick = () => {
document.querySelectorAll('.tab').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
activeTab = btn.dataset.tab;
renderEditor();
};
});
window.addEventListener('resize', autoScale);
loadSample(); // 载入财务总监数据
}
// 渲染左侧编辑器表单
function renderEditor() {
const container = document.getElementById('editorContent');
const b = state.basic || (state.basic = {});
const v = (x) => (x == null ? '' : String(x));
let html = '';
if (activeTab === 'basic') {
html = `
`;
} else if (activeTab === 'work') {
state.work.forEach((item, i) => {
html += ``;
});
html += ``;
} else if (activeTab === 'projects') {
state.projects.forEach((item, i) => {
html += ``;
});
html += ``;
} else if (activeTab === 'edu') {
state.education.forEach((item, i) => {
html += ``;
});
html += ``;
} else if (activeTab === 'skills') {
html += `请输入高管核心专业技能与战略视野 (熟练度推荐 3-5)。
`;
state.skills.forEach((item, i) => {
html += ``;
});
html += ``;
}
container.innerHTML = html;
}
// 交互响应
function handleInput(path, value) {
const parts = path.split('.'); let obj = state;
for (let i = 0; i < parts.length - 1; i++) obj = obj[parts[i]];
obj[parts[parts.length - 1]] = value;
updatePreview();
}
function addItem(listName, defaultObj) { state[listName].push(defaultObj); renderEditor(); updatePreview(); }
function removeItem(listName, index) { state[listName].splice(index, 1); renderEditor(); updatePreview(); }
function clearData() { if(confirm('⚠️ 确定清空当前简历的所有内容?')) { state = { basic:{name:'',title:'',phone:'',email:'',location:'',wechat:'',avatar:''}, summary:'', education:[], work:[], projects:[], skills:[] }; renderEditor(); updatePreview(); } }
// 默认财务总监级别案例
function loadSample() {
state = {
basic: { name: '张瀚文', title: '集团财务总监 (CFO) / 资本战略副总裁', phone: '139-9999-8888', email: 'hanwen.zhang@executive.com', location: '上海市 / 陆家嘴金融区', wechat: 'LinkedIn: Hanwen Zhang', avatar: '' },
summary: '逾18年大型跨国集团与行业龙头企业全面财务管理及资本运作经验。精通IPO统筹、跨境并购(M&A)、全球资金池搭建与税务合规。曾主导完成超5亿美元海外并购案及企业美股、港股双重上市筹备。具备宏观战略视野与百人以上全球化财务团队管理经验,致力于通过深刻的业财融合及财务数字化转型,强力驱动企业商业价值几何级增长。',
work: [
{ company: '鼎星控股集团 (市值千亿级上市企业)', title: '集团财务总监 (CFO)', date: '2019.05 - 至今', desc: '• 战略与资金池:全面负责集团战略财务规划,搭建全球资金集中管理平台,优化资产负债结构,每年为集团降低融资成本逾1.5亿元。\n• 业财数字化:主导财务共享服务中心(FSSC)及全链路数字化中台建设,实现全国40余家分子公司财务数据秒级穿透,整体运营合规效率提升60%。\n• 投资者关系:作为核心路演代表,对接百余家国际顶尖投行及PE/VC,成功完成两轮定增,累计募资超30亿人民币。' },
{ company: '远洋国际实业发展有限公司', title: '财务副总裁 (VP of Finance)', date: '2014.07 - 2019.04', desc: '• 跨境并购:主导收购德国某行业隐形冠军企业(交易规模约5.2亿美元),统筹前期财务尽职调查、税务结构设计及投后100天全球财务与业务体系融合。\n• 利润优化:建立基于阿米巴模式的全价值链预算风控体系,使公司连续三年净利润复合增长率(CAGR)保持在25%以上。' },
{ company: '普华永道 (PwC)', title: '高级审计经理', date: '2006.07 - 2014.06', desc: '• 领导30人专项团队,为多家世界500强企业及拟上市公司提供复杂年度审计、内控合规(SOX)及尽职调查咨询服务。\n• 成功护航4家高科技及高端制造企业于纳斯达克及香港联交所主板上市。' }
],
projects: [
{ name: '集团主体港交所IPO及全球发售', role: '上市筹备组常务副组长 / 财务总责', date: '2021.03 - 2022.08', desc: '统筹保荐人、审计师、律师等中介机构,梳理过去三年历史财务数据及VIE架构搭建。历时17个月,克服宏观市场波动,成功登陆港交所,超额认购达15倍,募集资金25亿港元。' },
{ name: '跨国供应链税务合规与合规重组', role: '项目总指挥', date: '2017.06 - 2018.12', desc: '针对欧美市场双反调查及BEPS合规要求,重组新加坡及欧洲关联交易定价模型,合法合规规避税务风险,整体有效税率(ETR)降低4.5%。' }
],
education: [
{ school: '中欧国际工商学院 (CEIBS)', major: '高级管理人员工商管理硕士 (EMBA)', degree: '硕士', date: '2016.09 - 2018.10', desc: '• 优秀毕业生,主攻企业战略与国际金融并购' },
{ school: '上海财经大学', major: '会计学 / 金融工程', degree: '本科', date: '2002.09 - 2006.06', desc: '• 中国注册会计师 (CICPA)\n• 特许金融分析师 (CFA) 持证人\n• 英国特许公认会计师 (ACCA) 会员' }
],
skills: [
{ name: '企业IPO与资本运作', level: 5 }, { name: '投融资与跨境并购 (M&A)', level: 5 }, { name: '全面预算与风险控制', level: 5 }, { name: '国际财务报告准则 (IFRS)', level: 5 }, { name: '业财融合与数字化转型', level: 5 }, { name: '全球税务合规处理', level: 4 }
]
};
renderEditor();
updatePreview();
}
function triggerUpload() {
const input = document.createElement('input'); input.type = 'file'; input.accept = 'image/*';
input.onchange = e => {
const file = e.target.files[0];
if (file) {
const reader = new FileReader();
reader.onload = ev => { state.basic.avatar = ev.target.result; updatePreview(); renderEditor(); }
reader.readAsDataURL(file);
}
}; input.click();
}
// 统一的联系方式列表(预览与导出共用,保证内容完全一致)
function getContacts() {
var arr = [];
if (state.basic.phone) arr.push('📞 ' + state.basic.phone);
if (state.basic.email) arr.push('✉️ ' + state.basic.email);
if (state.basic.location) arr.push('🏢 ' + state.basic.location);
if (state.basic.wechat) arr.push('🔗 ' + state.basic.wechat);
return arr;
}
// ==================== 核心:多模板渲染引擎(豪华升级版) ====================
function updatePreview() {
const page = document.getElementById('previewPage');
const c = tpl();
const e = (str) => str == null ? '' : String(str).replace(/[&<>"']/g, ch => ({'&':'&','<':'<','>':'>','"':'"',"'":"'"}[ch]));
// 主区 / 侧栏 两套调色上下文(解决深色侧栏文字不可见的老问题)
const mainCtx = { primary: c.primary, accent: c.accent || c.primary, accent2: c.accent2 || c.accent || c.primary, bg: c.bg, text: c.text };
const sideSolid = '#' + hex6(c.sideBg, 'F1F5F9');
const sideCtx = {
primary: c.sideText || c.primary,
accent: '#' + safeAccent(c.accent || c.primary, sideSolid, c.sideText || c.primary),
accent2: '#' + safeAccent(c.accent2 || c.accent || c.primary, sideSolid, c.sideText || c.primary),
bg: sideSolid,
text: c.sideText || c.text
};
// 豪华阴影预设
const premiumShadow = c.premium ? '0 8px 30px rgba(0,0,0,0.06), 0 2px 8px rgba(0,0,0,0.04)' : '0 2px 10px rgba(0,0,0,0.03)';
const cardRadius = c.cardRadius || 8;
// 1. 生成技能区 (豪华渲染)
const genSkills = (ctx) => {
ctx = ctx || mainCtx;
if(!state.skills.length) return '';
let html = '';
state.skills.forEach(sk => {
let lvl = Math.max(1, Math.min(5, parseInt(sk.level) || 3));
if (c.skillStyle === 'bar') {
// 豪华渐变进度条
html += `
${e(sk.name)}${'★'.repeat(lvl)}${'☆'.repeat(5-lvl)}
`;
} else if (c.skillStyle === 'dot') {
let dots = ''; for(let i=1; i<=5; i++) dots += ``;
html += ``;
} else if (c.skillStyle === 'tag') {
html += `${e(sk.name)}`;
} else {
html += `${e(sk.name)}
`;
}
});
return `${genSecTitle('核心专业矩阵', ctx)}
${html}
`;
};
// 2. 标题样式 (豪华升级)
const genSecTitle = (title, ctx) => {
ctx = ctx || mainCtx;
let tColor = ctx.primary;
let deco = c.titleDeco || '';
if (c.secStyle === 'left-border') return `${e(title)}
`;
if (c.secStyle === 'underline') return `${e(title)}
`;
if (c.secStyle === 'background') return `${e(title)}
`;
if (c.secStyle === 'center-line') return ``;
return `${e(title)}
`;
};
// 3. 高质量列表项 (豪华升级)
const genItem = (item, isEdu) => {
const title = isEdu ? item.school : (item.company || item.name);
const subtitle = isEdu ? ((item.major||'') + (item.degree ? ` · ${item.degree}` : '')) : (item.title || item.role);
let containerStyle = 'margin-bottom:26px;position:relative;';
if (c.itemStyle === 'card') {
containerStyle += `background:linear-gradient(135deg, ${c.text}06, ${c.text}03);padding:20px 22px;border-radius:${cardRadius}px;border-left:4px solid ${c.accent||c.primary};box-shadow:${premiumShadow};`;
}
else if (c.itemStyle === 'dot-line') {
containerStyle += `padding-left:26px;border-left:2px solid ${c.primary}26;position:relative;`;
}
let before = c.itemStyle === 'dot-line' ? `` : '';
if (c.itemStyle === 'card') {
before = ``;
}
return `${before}
${e(title)}
${e(item.date)}
${subtitle ? `
${e(subtitle)}
` : ''}
${item.desc ? `
${e(item.desc)}
` : ''}
`;
};
// 构建各主要区块
const eduHtml = state.education.length ? `${genSecTitle('教育背景与资质')}
${state.education.map(x=>genItem(x,true)).join('')}
` : '';
const workHtml = state.work.length ? `${genSecTitle('高管核心履历')}
${state.work.map(x=>genItem(x,false)).join('')}
` : '';
const projHtml = state.projects.length ? `${genSecTitle('重大战略与资本项目')}
${state.projects.map(x=>genItem(x,false)).join('')}
` : '';
const sumHtml = state.summary ? `${genSecTitle('高管执行摘要')}
${e(state.summary)}
` : '';
const contacts = getContacts();
const contactHtml = (arr, isSide) => arr.map(x => `${e(x)}
`).join('');
const avatarOf = (size, borderColor) => state.basic.avatar ? `
` : '';
let avatarHtml = avatarOf(112, (c.accent||c.primary) + '66');
let content = '';
// ========= 旗舰布局渲染 =========
if (c.layout === 'side') {
const sideContent = `
${avatarHtml}
${c.nameInSide ? `
${e(state.basic.name)}
${e(state.basic.title)}
` : ''}
${contactHtml(contacts, true)}
${genSkills(sideCtx)}
`;
const mainContent = `
${!c.nameInSide ? `${e(state.basic.name)}
${e(state.basic.title)}
` : ''}
${sumHtml}${workHtml}${projHtml}${eduHtml}
`;
content = `${!c.sideRight ? `
${sideContent}
` : ''}
${mainContent}
${c.sideRight ? `
${sideContent}
` : ''}
`;
} else if (c.layout === 'top') {
content = `${e(state.basic.name)}
${e(state.basic.title)}
${contactHtml(contacts, false)}
${avatarHtml ? `
${avatarHtml}
` : ''}
${sumHtml}
${workHtml}${projHtml}${eduHtml}
${genSkills()}
`;
} else if (c.layout === 'classic') {
content = `${c.headerBorder==='none'||!c.headerBorder?'
':''}
${e(state.basic.name)}
${e(state.basic.title)}
${contactHtml(contacts, false)}
${avatarHtml ? `
${avatarHtml}
` : ''}
${sumHtml}${workHtml}${projHtml}
`;
} else if (c.layout === 'hero') {
content = `
${avatarHtml ? `
${avatarOf(106, (c.accent||'#ffffff')+'99')}
` : ''}
${e(state.basic.name)}
${e(state.basic.title)}
${contactHtml(contacts, false)}
${sumHtml}${workHtml}${projHtml}
`;
} else if (c.layout === 'stripe') {
content = `
${e(state.basic.name)}
${e(state.basic.title)}
${contactHtml(contacts, false)}
${avatarHtml ? `
${avatarHtml}
` : ''}
${sumHtml}${workHtml}${projHtml}
`;
} else if (c.layout === 'split') {
content = `
${e(state.basic.name)}
${e(state.basic.title)}
${avatarHtml ? `
${avatarOf(90, (c.accent||c.primary)+'66')}
` : ''}
${contactHtml(contacts, true)}
${sumHtml}${workHtml}${projHtml}
`;
} else if (c.layout === 'magazine') {
content = `
${e(state.basic.name)}
${e(state.basic.title)}
${avatarHtml ? `
${avatarOf(82, (c.accent||'#ffffff')+'99')}
` : ''}
${contactHtml(contacts, true)}
${genSkills(sideCtx)}
${sumHtml}${workHtml}${projHtml}${eduHtml}
`;
} else if (c.layout === 'frame') {
content = `
${avatarHtml ? `
${avatarOf(94, (c.accent||c.primary)+'66')}
` : ''}
${e(state.basic.name)}
${e(state.basic.title)}
${contactHtml(contacts, false)}
${sumHtml}${workHtml}${projHtml}
`;
}
// 严格 A4 宽度包装,保证文本不换行错误
page.innerHTML = `${content}
`;
setTimeout(autoScale, 10);
}
function autoScale() {
const panel = document.getElementById('previewPanel'), wrapper = document.getElementById('previewWrapper');
if(!panel || !wrapper) return;
const scale = Math.min(1.2, Math.max(0.4, (panel.clientWidth - 100) / 794));
wrapper.style.transform = `scale(${scale})`;
}
/* ==================================================================================
★★★ 导出内核 A:通用工具(XML 转义 / Base64 / CRC32 / ZIP 打包 / 文件保存) ★★★
================================================================================== */
function xmlEsc(s) {
return String(s == null ? '' : s)
.replace(/[\u0000-\u0008\u000B\u000C\u000E-\u001F]/g, '')
.replace(/&/g, '&').replace(//g, '>')
.replace(/"/g, '"').replace(/'/g, ''');
}
function strToU8(s) { return new TextEncoder().encode(s); }
function b64ToU8(b64) {
var bin = atob(b64), len = bin.length, u8 = new Uint8Array(len);
for (var i = 0; i < len; i++) u8[i] = bin.charCodeAt(i);
return u8;
}
function parseDataUrl(dataUrl) {
if (!dataUrl || dataUrl.indexOf('data:') !== 0) return null;
var m = /^data:([^;,]+)(;base64)?,(.*)$/s.exec(dataUrl);
if (!m) return null;
var mime = m[1].toLowerCase();
var bytes = m[2] ? b64ToU8(m[3]) : strToU8(decodeURIComponent(m[3]));
var ext = mime.indexOf('png') >= 0 ? 'png' : (mime.indexOf('gif') >= 0 ? 'gif' : (mime.indexOf('bmp') >= 0 ? 'bmp' : 'jpeg'));
return { mime: mime, ext: ext, bytes: bytes };
}
var CRC_TABLE = (function () {
var t = new Uint32Array(256);
for (var n = 0; n < 256; n++) {
var c = n;
for (var k = 0; k < 8; k++) c = (c & 1) ? (0xEDB88320 ^ (c >>> 1)) : (c >>> 1);
t[n] = c >>> 0;
}
return t;
})();
function crc32(u8) {
var c = 0xFFFFFFFF;
for (var i = 0; i < u8.length; i++) c = CRC_TABLE[(c ^ u8[i]) & 0xFF] ^ (c >>> 8);
return (c ^ 0xFFFFFFFF) >>> 0;
}
/** 极简 ZIP 打包器(Store 模式,Office 完全兼容) */
function makeZip(entries) {
var enc = new TextEncoder(), parts = [], central = [], offset = 0;
entries.forEach(function (e) {
var nameBytes = enc.encode(e.name);
var data = (e.data instanceof Uint8Array) ? e.data : strToU8(e.data);
var crc = crc32(data);
var lh = new Uint8Array(30 + nameBytes.length), dv = new DataView(lh.buffer);
dv.setUint32(0, 0x04034b50, true);
dv.setUint16(4, 20, true); dv.setUint16(6, 0x0800, true); dv.setUint16(8, 0, true);
dv.setUint16(10, 0, true); dv.setUint16(12, 0x21, true);
dv.setUint32(14, crc, true); dv.setUint32(18, data.length, true); dv.setUint32(22, data.length, true);
dv.setUint16(26, nameBytes.length, true); dv.setUint16(28, 0, true);
lh.set(nameBytes, 30);
parts.push(lh, data);
var ch = new Uint8Array(46 + nameBytes.length), cv = new DataView(ch.buffer);
cv.setUint32(0, 0x02014b50, true);
cv.setUint16(4, 20, true); cv.setUint16(6, 20, true); cv.setUint16(8, 0x0800, true);
cv.setUint16(10, 0, true); cv.setUint16(12, 0, true); cv.setUint16(14, 0x21, true);
cv.setUint32(16, crc, true); cv.setUint32(20, data.length, true); cv.setUint32(24, data.length, true);
cv.setUint16(28, nameBytes.length, true); cv.setUint16(30, 0, true); cv.setUint16(32, 0, true);
cv.setUint16(34, 0, true); cv.setUint16(36, 0, true); cv.setUint32(38, 0, true);
cv.setUint32(42, offset, true);
ch.set(nameBytes, 46);
central.push(ch);
offset += lh.length + data.length;
});
var cdSize = central.reduce(function (a, b) { return a + b.length; }, 0);
var eocd = new Uint8Array(22), ev = new DataView(eocd.buffer);
ev.setUint32(0, 0x06054b50, true);
ev.setUint16(4, 0, true); ev.setUint16(6, 0, true);
ev.setUint16(8, entries.length, true); ev.setUint16(10, entries.length, true);
ev.setUint32(12, cdSize, true); ev.setUint32(16, offset, true); ev.setUint16(20, 0, true);
var total = offset + cdSize + 22, out = new Uint8Array(total), p = 0;
parts.forEach(function (x) { out.set(x, p); p += x.length; });
central.forEach(function (x) { out.set(x, p); p += x.length; });
out.set(eocd, p);
return out;
}
function saveBytes(bytes, filename, mime) {
var blob = new Blob([bytes], { type: mime });
var url = URL.createObjectURL(blob);
var a = document.createElement('a');
a.href = url; a.download = filename;
document.body.appendChild(a); a.click(); document.body.removeChild(a);
setTimeout(function () { URL.revokeObjectURL(url); }, 1500);
}
function safeName() {
return String(state.basic.name || 'CFO简历').replace(/[\\/:*?"<>|]/g, '_');
}
/* ==================================================================================
★★★ 导出内核 B:调色板(把预览里的透明度色彩折算为 Office 实色) ★★★
================================================================================== */
function mainPalette(c) {
var bg = hex6(c.bg, 'FFFFFF'), text = hex6(c.text, '1E293B'), primary = hex6(c.primary, '0F172A');
var accent = safeAccent(c.accent || c.primary, bg, primary);
var accent2 = safeAccent(c.accent2 || c.accent || c.primary, bg, primary);
return {
bg: bg, text: text, primary: primary, accent: accent, accent2: accent2,
soft: fadeHex(text, bg, 0.80), // 预览里的 text + CC
body: fadeHex(text, bg, 0.70), // text + B3
muted: fadeHex(text, bg, 0.60), // text + 99
card: fadeHex(text, bg, 0.05), // text + 06
card2: fadeHex(text, bg, 0.03), // text + 03 (渐变第二色)
track: fadeHex(primary, bg, 0.15),// primary + 22
line: fadeHex(primary, bg, 0.22),// primary + 33
tag: fadeHex(primary, bg, 0.08) // primary + 11
};
}
function sidePalette(c) {
var bg = hex6(c.sideBg, 'F1F5F9'), text = hex6(c.sideText, '0F172A');
var accent = safeAccent(c.accent || c.sideText, bg, text);
var accent2 = safeAccent(c.accent2 || c.accent || c.sideText, bg, text);
return {
bg: bg, text: text, primary: text, accent: accent, accent2: accent2,
soft: fadeHex(text, bg, 0.88), body: fadeHex(text, bg, 0.80), muted: fadeHex(text, bg, 0.70),
card: fadeHex(text, bg, 0.06), card2: fadeHex(text, bg, 0.03), track: fadeHex(text, bg, 0.22), line: fadeHex(text, bg, 0.26),
tag: fadeHex(text, bg, 0.10)
};
}
function bandPalette(bgRaw, textRaw, accentRaw) {
var bg = hex6(bgRaw, '1E3A8A'), text = hex6(textRaw, 'FFFFFF');
var accent = safeAccent(accentRaw || textRaw, bg, text);
var accent2 = safeAccent(accentRaw || textRaw, bg, text);
return {
bg: bg, text: text, primary: text, accent: accent, accent2: accent2,
soft: fadeHex(text, bg, 0.92), body: fadeHex(text, bg, 0.85), muted: fadeHex(text, bg, 0.78),
card: fadeHex(text, bg, 0.08), card2: fadeHex(text, bg, 0.04), track: fadeHex(text, bg, 0.25), line: fadeHex(text, bg, 0.3),
tag: fadeHex(text, bg, 0.12)
};
}
function officeFont(c) {
var f = String(c.font || '').toLowerCase();
if (f.indexOf('georgia') >= 0) return 'Georgia';
if (f.indexOf('times') >= 0) return 'Times New Roman';
if (f.indexOf('mono') >= 0) return 'Consolas';
return 'Microsoft YaHei';
}
/* ==================================================================================
★★★ 导出内核 C:真实 .docx(WordprocessingML)生成器 ★★★
—— A4 零边距 / 表格版式还原 / 颜色字号一比一 / 头像圆形嵌入 / 文字可编辑
—— 豪华升级:渐变色带模拟 / 装饰线 / 双色点缀 / 精致间距
================================================================================== */
var T = function (px) { return Math.round(px * 15); }; // px -> twips
var HALFPT = function (px) { return Math.max(2, Math.round(px * 1.5)); }; // px -> half-point
function wRun(text, o) {
o = o || {};
var fnt = o.font || 'Microsoft YaHei';
var rPr = '';
rPr += '';
if (o.bold) rPr += '';
if (o.italic) rPr += '';
if (o.color) rPr += '';
if (o.spacing) rPr += '';
rPr += '';
if (o.underline) rPr += '';
if (o.shd) rPr += '';
rPr += '';
var parts = String(text == null ? '' : text).split('\n'), body = '';
for (var i = 0; i < parts.length; i++) {
if (i) body += '';
body += '' + xmlEsc(parts[i]) + '';
}
return '' + rPr + body + '';
}
function wPara(runs, o) {
o = o || {};
var pPr = '';
if (o.borderTop || o.borderLeft || o.borderBottom) {
pPr += '';
if (o.borderTop) pPr += '';
if (o.borderLeft) pPr += '';
if (o.borderBottom) pPr += '';
pPr += '';
}
if (o.shd) pPr += '';
if (o.tabs && o.tabs.length) {
pPr += '' + o.tabs.map(function (t) {
return '';
}).join('') + '';
}
pPr += '';
if (o.indentLeft || o.indentRight) {
pPr += '';
}
if (o.align) pPr += '';
var content = Array.isArray(runs) ? runs.join('') : (runs || '');
return '' + pPr + '' + content + '';
}
function wSpacer(px) {
return ' ';
}
function wTable(rows, opt) {
opt = opt || {};
var grid = opt.grid || [11906];
var x = '';
x += '';
if (opt.ind) x += '';
x += opt.borders || '';
x += '';
x += '';
x += '';
x += '' + grid.map(function (w) { return ''; }).join('') + '';
rows.forEach(function (r) {
x += '';
if (r.height) x += '';
r.cells.forEach(function (cl) {
x += '';
x += '';
if (cl.span) x += '';
if (cl.borders) x += cl.borders;
if (cl.shd) x += '';
x += '';
if (cl.vAlign) x += '';
x += '';
x += (cl.content && cl.content.length ? cl.content : wPara(''));
x += '';
});
x += '';
});
return x + '';
}
function wImage(rid, px, align, id) {
var emu = Math.round(px * 9525);
return '' + (align ? '' : '') + ''
+ ''
+ ''
+ ''
+ ''
+ ''
+ ''
+ ''
+ ''
+ ''
+ '';
}
/* ---------- docx 内容片段:区块标题 / 条目 / 技能 (豪华升级) ---------- */
function dxSecTitle(title, c, P, w, fnt) {
var s = c.secStyle;
if (s === 'background') {
return wPara(wRun(' ' + title + ' ', { size: 14, bold: true, color: bestOn(P.primary), spacing: 30, shd: P.primary, font: fnt }),
{ before: 8, after: 14 });
}
if (s === 'left-border') {
// 豪华左边框 + 装饰点
return wPara(wRun(title, { size: 17, bold: true, color: P.primary, spacing: 30, font: fnt }),
{ borderLeft: { sz: 30, color: P.accent, space: 6 }, indentLeft: 170, before: 8, after: 14, line: 1.15 });
}
if (s === 'underline') {
// 豪华下划线
return wPara(wRun(title, { size: 17, bold: true, color: P.primary, spacing: 30, font: fnt }),
{ borderBottom: { sz: 14, color: P.accent, space: 5 }, before: 8, after: 14, line: 1.15 });
}
if (s === 'center-line') {
// 豪华居中线
return wPara(wRun(title, { size: 17, bold: true, color: P.primary, spacing: 30, font: fnt }),
{ align: 'center', borderBottom: { sz: 6, color: fadeHex(P.primary, P.bg, 0.4), space: 5 }, before: 8, after: 16, line: 1.15 });
}
return wPara(wRun(title, { size: 17, bold: true, color: P.primary, spacing: 30, font: fnt }), { before: 8, after: 14, line: 1.15 });
}
function dxItem(item, isEdu, c, P, cw, fnt) {
var title = isEdu ? (item.school || '') : (item.company || item.name || '');
var subtitle = isEdu ? ((item.major || '') + (item.degree ? ' · ' + item.degree : '')) : (item.title || item.role || '');
var card = c.itemStyle === 'card', dotline = c.itemStyle === 'dot-line';
var shd = card ? P.card : null;
var bl = card ? { sz: 24, color: P.accent, space: 7 } : (dotline ? { sz: 12, color: P.line, space: 7 } : null);
var indL = card ? 230 : (dotline ? 260 : 0);
var indR = card ? 200 : 0;
var tabPos = Math.max(600, cw - indL - indR);
var out = '';
out += wPara([
wRun(title, { size: 15.5, bold: true, color: P.primary, spacing: 8, font: fnt }),
'',
wRun(item.date || '', { size: 12, bold: true, color: P.muted, spacing: 15, font: 'Consolas', shd: P.tag })
], { shd: shd, borderLeft: bl, indentLeft: indL, indentRight: indR, tabs: [{ val: 'right', pos: tabPos }], before: card ? 10 : 0, after: 3, line: 1.25 });
if (subtitle) {
out += wPara([
wRun(' ', { size: 12, font: fnt, shd: P.accent }),
wRun(' ' + subtitle, { size: 14, bold: true, color: P.soft, font: fnt })
], { shd: shd, borderLeft: bl, indentLeft: indL, indentRight: indR, after: 4, line: 1.25 });
}
if (item.desc) {
out += wPara(wRun(item.desc, { size: 13, color: P.body, font: fnt }),
{ shd: shd, borderLeft: bl, indentLeft: indL, indentRight: indR, after: card ? 10 : 0, line: 1.35, align: 'left' });
}
out += wSpacer(14);
return out;
}
function dxSkills(c, P, w, fnt) {
if (!state.skills.length) return '';
var s = dxSecTitle('核心专业矩阵', c, P, w, fnt), st = c.skillStyle;
if (st === 'tag') {
var runs = [];
state.skills.forEach(function (sk) {
runs.push(wRun(' ' + (sk.name || '') + ' ', { size: 12, bold: true, color: P.primary, shd: P.tag, font: fnt }));
runs.push(wRun(' ', { size: 12, font: fnt }));
});
s += wPara(runs, { after: 18, line: 1.4 });
} else if (st === 'text') {
var r2 = [];
state.skills.forEach(function (sk, i) {
r2.push(wRun(sk.name || '', { size: 13, bold: true, color: P.primary, underline: P.accent, font: fnt }));
if (i < state.skills.length - 1) r2.push(wRun(' ', { size: 13, font: fnt }));
});
s += wPara(r2, { after: 18, line: 1.4 });
} else if (st === 'dot') {
state.skills.forEach(function (sk) {
var lv = Math.max(1, Math.min(5, parseInt(sk.level) || 3));
s += wPara([
wRun(sk.name || '', { size: 13, bold: true, color: P.primary, font: fnt }),
'',
wRun(new Array(lv + 1).join('●'), { size: 12, color: P.accent, font: fnt }),
wRun(new Array(5 - lv + 1).join('●'), { size: 12, color: P.track, font: fnt })
], { tabs: [{ val: 'right', pos: w }], after: 8, line: 1.35 });
});
s += wSpacer(10);
} else { // bar
state.skills.forEach(function (sk) {
var lv = Math.max(1, Math.min(5, parseInt(sk.level) || 3));
s += wPara([
wRun(sk.name || '', { size: 12.5, bold: true, color: P.primary, font: fnt }),
'',
wRun(new Array(lv + 1).join('★'), { size: 11, color: P.accent, font: fnt }),
wRun(new Array(5 - lv + 1).join('☆'), { size: 11, color: P.track, font: fnt })
], { tabs: [{ val: 'right', pos: w }], after: 2, line: 1.2 });
var fill = Math.round(w * lv / 5), rest = w - fill, cells = [];
if (rest < 60) { cells.push({ w: w, shd: P.accent, content: wSpacer(4) }); }
else if (fill < 60) { cells.push({ w: w, shd: P.track, content: wSpacer(4) }); }
else { cells.push({ w: fill, shd: P.accent, content: wSpacer(4) }); cells.push({ w: rest, shd: P.track, content: wSpacer(4) }); }
s += wTable([{ cells: cells, height: 80 }], { grid: cells.map(function (x) { return x.w; }) });
s += wSpacer(11);
});
}
return s;
}
function dxHeadName(P, size, align, fnt, spacing) {
return wPara(wRun(state.basic.name || '', { size: size, bold: true, color: P.primary, spacing: spacing == null ? 60 : spacing, font: fnt }),
{ align: align, after: 6, line: 1.15 });
}
function dxHeadTitle(P, size, align, fnt) {
return wPara(wRun(state.basic.title || '', { size: size, bold: true, color: P.accent, spacing: 30, font: fnt }),
{ align: align, after: 10, line: 1.3 });
}
function dxContactsInline(P, align, fnt) {
var arr = getContacts();
if (!arr.length) return '';
return wPara(wRun(arr.join(' '), { size: 12.5, color: P.muted, font: fnt }), { align: align, after: 4, line: 1.5 });
}
function dxContactsBlock(P, fnt) {
return getContacts().map(function (x) {
return wPara(wRun(x, { size: 12, color: P.body, font: fnt }), { after: 8, line: 1.45 });
}).join('');
}
/* ---------- docx 主构建 ---------- */
function buildDocxBytes() {
var c = tpl();
var fnt = officeFont(c);
var P = mainPalette(c), S = sidePalette(c);
var FULL = 11906; // A4 宽 twips
var img = parseDataUrl(state.basic.avatar);
var IMG_RID = 'rId100';
var imgId = 1;
function mainBody(w, opt) {
opt = opt || {};
var s = '';
if (state.summary) {
s += dxSecTitle('高管执行摘要', c, P, w, fnt);
s += wPara(wRun(state.summary, { size: 13.5, color: P.soft, font: fnt }), { after: 24, line: 1.4, align: 'both', borderLeft: { sz: 18, color: fadeHex(P.accent, P.bg, 0.2), space: 8 }, indentLeft: 200 });
}
if (state.work.length) {
s += dxSecTitle('高管核心履历', c, P, w, fnt);
s += state.work.map(function (x) { return dxItem(x, false, c, P, w, fnt); }).join('');
s += wSpacer(10);
}
if (state.projects.length) {
s += dxSecTitle('重大战略与资本项目', c, P, w, fnt);
s += state.projects.map(function (x) { return dxItem(x, false, c, P, w, fnt); }).join('');
s += wSpacer(10);
}
if (!opt.skipEdu && state.education.length) {
s += dxSecTitle('教育背景与资质', c, P, w, fnt);
s += state.education.map(function (x) { return dxItem(x, true, c, P, w, fnt); }).join('');
s += wSpacer(10);
}
return s;
}
function eduBlock(w) {
if (!state.education.length) return '';
return dxSecTitle('教育背景与资质', c, P, w, fnt)
+ state.education.map(function (x) { return dxItem(x, true, c, P, w, fnt); }).join('') + wSpacer(8);
}
/** 底部双栏:左=教育,右=技能 */
function twoColFooter(totalW) {
var gap = T(44), left = Math.round((totalW - gap) / 2), right = totalW - gap - left;
var l = eduBlock(left), r = dxSkills(c, P, right, fnt);
if (!l && !r) return '';
return wTable([{ cells: [
{ w: left, content: l || wPara('') },
{ w: gap, content: wPara('') },
{ w: right, content: r || wPara('') }
] }], { grid: [left, gap, right] }) + wSpacer(4);
}
function sideColumn(w, showName) {
var s = '';
if (img) { s += wImage(IMG_RID, 104, 'center', imgId++); s += wSpacer(12); }
if (showName) {
s += wPara(wRun(state.basic.name || '', { size: 27, bold: true, color: S.text, spacing: 45, font: fnt }), { align: 'center', after: 6, line: 1.15 });
s += wPara(wRun(state.basic.title || '', { size: 13, bold: true, color: S.accent, spacing: 15, font: fnt }), { align: 'center', after: 14, line: 1.35 });
}
s += wSpacer(16);
s += wPara('', { borderTop: { sz: 6, color: S.line, space: 1 }, after: 0, line: 0.2 });
s += wSpacer(12);
s += dxContactsBlock(S, fnt);
s += wSpacer(6);
s += wPara('', { borderTop: { sz: 6, color: S.line, space: 1 }, after: 0, line: 0.2 });
s += wSpacer(16);
s += dxSkills(c, S, w, fnt);
return s;
}
var body = '';
/* ===== side ===== */
if (c.layout === 'side') {
var sw = T(290), mw = FULL - sw;
var sideCellW = sw - T(70), mainCellW = mw - T(100);
var sideCell = { w: sw, shd: S.bg, ml: T(35), mr: T(35), mt: T(46), mb: T(40), content: sideColumn(sideCellW, !!c.nameInSide) };
var mainInner = '';
if (!c.nameInSide) {
mainInner += dxHeadName(P, 32, 'left', fnt, 70);
mainInner += dxHeadTitle(P, 17, 'left', fnt);
mainInner += wPara('', { borderBottom: { sz: 18, color: P.primary, space: 1 }, after: 0, line: 0.2 });
mainInner += wSpacer(20);
}
mainInner += mainBody(mainCellW);
var mainCell = { w: mw, shd: P.bg, ml: T(50), mr: T(50), mt: T(54), mb: T(40), content: mainInner };
body += wTable([{ cells: c.sideRight ? [mainCell, sideCell] : [sideCell, mainCell] }],
{ grid: c.sideRight ? [mw, sw] : [sw, mw] });
/* ===== top ===== */
} else if (c.layout === 'top') {
var B = bandPalette(c.topBg, c.topText, c.accent);
var hw = FULL - T(120);
var head = '';
var headLeft = dxHeadName(B, 34, 'left', fnt, 70) + dxHeadTitle(B, 17, 'left', fnt) + dxContactsInline(B, 'left', fnt);
if (img) {
var lw = hw - T(140), rw = T(140);
head = wTable([{ cells: [
{ w: lw, content: headLeft, vAlign: 'center' },
{ w: rw, content: wImage(IMG_RID, 104, 'right', imgId++), vAlign: 'center' }
] }], { grid: [lw, rw] }) + wSpacer(2);
} else head = headLeft;
var bodyW = FULL - T(120);
var gapW = T(48), colL = Math.round((bodyW - gapW) * 0.7), colR = bodyW - gapW - colL;
var inner = '';
if (state.summary) {
inner += dxSecTitle('高管执行摘要', c, P, bodyW, fnt);
inner += wPara(wRun(state.summary, { size: 13.5, color: P.soft, font: fnt }), { after: 24, line: 1.9, align: 'both', borderLeft: { sz: 18, color: fadeHex(P.accent, P.bg, 0.2), space: 8 }, indentLeft: 200 });
}
var leftStack = '';
if (state.work.length) { leftStack += dxSecTitle('高管核心履历', c, P, colL, fnt) + state.work.map(function (x) { return dxItem(x, false, c, P, colL, fnt); }).join('') + wSpacer(8); }
if (state.projects.length) { leftStack += dxSecTitle('重大战略与资本项目', c, P, colL, fnt) + state.projects.map(function (x) { return dxItem(x, false, c, P, colL, fnt); }).join('') + wSpacer(8); }
if (state.education.length) { leftStack += dxSecTitle('教育背景与资质', c, P, colL, fnt) + state.education.map(function (x) { return dxItem(x, true, c, P, colL, fnt); }).join('') + wSpacer(8); }
inner += wTable([{ cells: [
{ w: colL, content: leftStack || wPara('') },
{ w: gapW, content: wPara('') },
{ w: colR, content: dxSkills(c, P, colR, fnt) || wPara('') }
] }], { grid: [colL, gapW, colR] }) + wSpacer(4);
var topBorders = c.topBorderBottom ? null : '';
body += wTable([
{ cells: [{ w: FULL, shd: B.bg, ml: T(60), mr: T(60), mt: T(46), mb: T(44), borders: topBorders, content: head }] },
{ cells: [{ w: FULL, shd: P.bg, ml: T(60), mr: T(60), mt: T(44), mb: T(40), content: inner }] }
], { grid: [FULL] });
/* ===== classic ===== */
} else if (c.layout === 'classic') {
var cw = FULL - T(140);
var centerH = c.headerAlign === 'center';
var hdr = '';
var hStack = dxHeadName(P, 34, centerH ? 'center' : 'left', fnt, 80)
+ dxHeadTitle(P, 17, centerH ? 'center' : 'left', fnt)
+ dxContactsInline(P, centerH ? 'center' : 'left', fnt);
if (img && !centerH) {
var l2 = cw - T(140), r2w = T(140);
hdr = wTable([{ cells: [
{ w: l2, content: hStack, vAlign: 'center' },
{ w: r2w, content: wImage(IMG_RID, 104, 'right', imgId++), vAlign: 'center' }
] }], { grid: [l2, r2w] }) + wSpacer(2);
} else if (img && centerH) {
hdr = wImage(IMG_RID, 100, 'center', imgId++) + wSpacer(12) + hStack;
} else hdr = hStack;
var hbColor = hex6((c.headerBorder || '').match(/#[0-9a-fA-F]{3,6}/) ? c.headerBorder.match(/#[0-9a-fA-F]{3,6}/)[0] : c.primary, P.primary);
var hbSize = /(\d+)px/.exec(c.headerBorder || '') ? parseInt(/(\d+)px/.exec(c.headerBorder)[1], 10) : 0;
if (hbSize > 0) hdr += wPara('', { borderBottom: { sz: Math.max(6, hbSize * 8), color: hbColor, space: 1 }, after: 0, line: 0.2 });
else hdr += wPara('', { borderBottom: { sz: 6, color: P.line, space: 1 }, after: 0, line: 0.2 });
hdr += wSpacer(26);
var main = hdr;
if (state.summary) { main += dxSecTitle('高管执行摘要', c, P, cw, fnt) + wPara(wRun(state.summary, { size: 13.5, color: P.soft, font: fnt }), { after: 24, line: 1.9, align: 'both', borderLeft: { sz: 18, color: fadeHex(P.accent, P.bg, 0.2), space: 8 }, indentLeft: 200 }); }
if (state.work.length) { main += dxSecTitle('高管核心履历', c, P, cw, fnt) + state.work.map(function (x) { return dxItem(x, false, c, P, cw, fnt); }).join('') + wSpacer(8); }
if (state.projects.length) { main += dxSecTitle('重大战略与资本项目', c, P, cw, fnt) + state.projects.map(function (x) { return dxItem(x, false, c, P, cw, fnt); }).join('') + wSpacer(8); }
main += twoColFooter(cw);
body += wTable([{ cells: [{ w: FULL, shd: P.bg, ml: T(70), mr: T(70), mt: T(54), mb: T(44), content: main }] }], { grid: [FULL] });
/* ===== hero (全新) ===== */
} else if (c.layout === 'hero') {
var HB = bandPalette(c.topBg, c.topText, c.accent);
var heroW = FULL - T(120);
var hero = '';
if (img) hero += wImage(IMG_RID, 100, 'center', imgId++) + wSpacer(14);
hero += wPara(wRun(state.basic.name || '', { size: 36, bold: true, color: HB.text, spacing: 120, font: fnt }), { align: 'center', after: 8, line: 1.15 });
hero += wPara(wRun(state.basic.title || '', { size: 15.5, bold: true, color: HB.accent, spacing: 60, font: fnt }), { align: 'center', after: 14, line: 1.3 });
hero += dxContactsInline(HB, 'center', fnt);
var hb = c.topBorderBottom && /(\d+)px/.test(c.topBorderBottom) ? parseInt(/(\d+)px/.exec(c.topBorderBottom)[1], 10) : 0;
var hbc = c.topBorderBottom && /#[0-9a-fA-F]{3,6}/.test(c.topBorderBottom) ? hex6(c.topBorderBottom.match(/#[0-9a-fA-F]{3,6}/)[0]) : HB.accent;
var heroCellBorders = hb > 0 ? '' : '';
var bodyW2 = FULL - T(128);
var m2 = mainBody(bodyW2, { skipEdu: true }) + twoColFooter(bodyW2);
body += wTable([
{ cells: [{ w: FULL, shd: HB.bg, ml: T(60), mr: T(60), mt: T(50), mb: T(44), borders: heroCellBorders, content: hero }] },
{ cells: [{ w: FULL, shd: P.bg, ml: T(64), mr: T(64), mt: T(42), mb: T(40), content: m2 }] }
], { grid: [FULL] });
/* ===== stripe (全新) ===== */
} else if (c.layout === 'stripe') {
var stW = T(c.stripeWidth || 18), restW = FULL - stW;
var innerW = restW - T(116);
var sHead = '';
var sStack = dxHeadName(P, 34, 'left', fnt, 70) + dxHeadTitle(P, 16.5, 'left', fnt) + dxContactsInline(P, 'left', fnt);
if (img) {
var sl = innerW - T(130), sr = T(130);
sHead = wTable([{ cells: [
{ w: sl, content: sStack, vAlign: 'center' },
{ w: sr, content: wImage(IMG_RID, 100, 'right', imgId++), vAlign: 'center' }
] }], { grid: [sl, sr] }) + wSpacer(2);
} else sHead = sStack;
sHead += wSpacer(14);
sHead += wPara('', { borderBottom: { sz: 18, color: P.accent, space: 1 }, after: 0, line: 0.2 });
sHead += wSpacer(24);
var sBody = sHead + mainBody(innerW, { skipEdu: true }) + twoColFooter(innerW);
body += wTable([{ cells: [
{ w: stW, shd: hex6(c.stripeBg, P.primary), content: wPara('') },
{ w: restW, shd: P.bg, ml: T(58), mr: T(58), mt: T(52), mb: T(40), content: sBody }
] }], { grid: [stW, restW] });
/* ===== split (全新) ===== */
} else if (c.layout === 'split') {
var LP = bandPalette(c.splitLeftBg || c.primary, c.splitLeftText || '#ffffff', c.accent);
var RP = bandPalette(c.splitRightBg || '#f1f5f9', c.splitRightText || c.text, c.accent);
var lW = Math.round(FULL * 0.545), rW = FULL - lW;
var lIn = wPara(wRun(state.basic.name || '', { size: 32, bold: true, color: LP.text, spacing: 90, font: fnt }), { after: 8, line: 1.15 })
+ wPara(wRun(state.basic.title || '', { size: 15, bold: true, color: LP.accent, spacing: 35, font: fnt }), { after: 0, line: 1.4 });
var rIn = '';
if (img) rIn += wImage(IMG_RID, 82, 'left', imgId++) + wSpacer(12);
rIn += getContacts().map(function (x) {
return wPara(wRun(x, { size: 12, bold: true, color: RP.body, font: fnt }), { after: 7, line: 1.5 });
}).join('');
var bw = FULL - T(120);
var sBody2 = mainBody(bw, { skipEdu: true }) + twoColFooter(bw);
body += wTable([{ cells: [
{ w: lW, shd: LP.bg, ml: T(42), mr: T(30), mt: T(48), mb: T(46), content: lIn, vAlign: 'center' },
{ w: rW, shd: RP.bg, ml: T(38), mr: T(32), mt: T(48), mb: T(46), content: rIn, vAlign: 'center' }
] }], { grid: [lW, rW] });
body += wTable([{ cells: [{ w: FULL, shd: P.bg, ml: T(60), mr: T(60), mt: T(42), mb: T(40), content: sBody2 }] }], { grid: [FULL] });
/* ===== magazine (全新) ===== */
} else if (c.layout === 'magazine') {
var MB = bandPalette(c.topBg, c.topText, c.accent);
var mHeadW = FULL - T(100);
var mLeft = wPara(wRun(state.basic.name || '', { size: 28, bold: true, color: MB.text, spacing: 80, font: fnt }), { after: 5, line: 1.15 })
+ wPara(wRun(state.basic.title || '', { size: 14, bold: true, color: MB.accent, spacing: 30, font: fnt }), { after: 0, line: 1.35 });
var mHead;
if (img) {
var ml2 = mHeadW - T(110), mr2 = T(110);
mHead = wTable([{ cells: [
{ w: ml2, content: mLeft, vAlign: 'center' },
{ w: mr2, content: wImage(IMG_RID, 78, 'right', imgId++), vAlign: 'center' }
] }], { grid: [ml2, mr2] }) + wSpacer(2);
} else mHead = mLeft;
var msW = T(252), mmW = FULL - msW;
var msInner = msW - T(52), mmInner = mmW - T(84);
var sideStack = getContacts().map(function (x) {
return wPara(wRun(x, { size: 12, color: S.body, font: fnt }), { after: 8, line: 1.5 });
}).join('');
sideStack += wSpacer(10);
sideStack += wPara('', { borderTop: { sz: 6, color: S.line, space: 1 }, after: 0, line: 0.2 });
sideStack += wSpacer(18);
sideStack += dxSkills(c, S, msInner, fnt);
body += wTable([{ cells: [{ w: FULL, shd: MB.bg, ml: T(50), mr: T(50), mt: T(30), mb: T(30), borders: '', content: mHead }] }], { grid: [FULL] });
body += wTable([{ cells: [
{ w: msW, shd: S.bg, ml: T(26), mr: T(26), mt: T(34), mb: T(34), content: sideStack },
{ w: mmW, shd: P.bg, ml: T(42), mr: T(42), mt: T(36), mb: T(34), content: mainBody(mmInner) }
] }], { grid: [msW, mmW] });
/* ===== frame (全新) ===== */
} else if (c.layout === 'frame') {
var pad = T(26), tblW = FULL - pad * 2;
var fbSz = /(\d+)px/.test(c.frameBorder || '') ? parseInt(/(\d+)px/.exec(c.frameBorder)[1], 10) : 2;
var fbCol = /#[0-9a-fA-F]{3,6}/.test(c.frameBorder || '') ? hex6(c.frameBorder.match(/#[0-9a-fA-F]{3,6}/)[0]) : P.primary;
var fBorders = ''
+ ['top', 'left', 'bottom', 'right'].map(function (k) { return ''; }).join('')
+ '';
var fInner = tblW - T(96);
var fHead = '';
if (img) fHead += wImage(IMG_RID, 88, 'center', imgId++) + wSpacer(12);
fHead += wPara(wRun(state.basic.name || '', { size: 32, bold: true, color: P.primary, spacing: 90, font: fnt }), { align: 'center', after: 7, line: 1.15 });
fHead += wPara(wRun(state.basic.title || '', { size: 15, bold: true, color: P.accent, spacing: 45, font: fnt }), { align: 'center', after: 10, line: 1.3 });
fHead += dxContactsInline(P, 'center', fnt);
fHead += wSpacer(12);
fHead += wPara('', { borderBottom: { sz: 6, color: P.line, space: 1 }, after: 0, line: 0.2 });
fHead += wSpacer(22);
var fBody = fHead + mainBody(fInner, { skipEdu: true }) + twoColFooter(fInner);
body += wTable([{ cells: [{ w: tblW, shd: P.bg, ml: T(48), mr: T(48), mt: T(40), mb: T(38), content: fBody }] }],
{ grid: [tblW], ind: pad, borders: fBorders });
}
body += wSpacer(1);
body += '';
var documentXml = ''
+ ''
+ '' + body + '';
var stylesXml = ''
+ ''
+ ''
+ ''
+ ''
+ ''
+ ''
+ ''
+ '';
var contentTypes = ''
+ ''
+ ''
+ ''
+ ''
+ ''
+ ''
+ ''
+ ''
+ ''
+ '';
var rootRels = ''
+ ''
+ ''
+ '';
var docRels = ''
+ ''
+ ''
+ (img ? '' : '')
+ '';
var files = [
{ name: '[Content_Types].xml', data: contentTypes },
{ name: '_rels/.rels', data: rootRels },
{ name: 'word/_rels/document.xml.rels', data: docRels },
{ name: 'word/document.xml', data: documentXml },
{ name: 'word/styles.xml', data: stylesXml }
];
if (img) files.push({ name: 'word/media/image1.' + img.ext, data: img.bytes });
return makeZip(files);
}
/* ==================================================================================
★★★ 导出内核 D:真实 .xlsx(SpreadsheetML)生成器 ★★★
—— 真实单元格文本 + 完整样式表 + 网格化版面 + 富文本技能条 + A4 打印设置
—— 豪华升级:渐变色带还原 / 装饰边框 / 双色点缀 / 精致行高
================================================================================== */
function XlStyles() {
this.fonts = [''];
this.fills = ['', ''];
this.borders = [''];
this.xfs = [''];
this.cache = {};
}
XlStyles.prototype.get = function (o) {
o = o || {};
var key = JSON.stringify(o);
if (this.cache[key] != null) return this.cache[key];
var f = '';
if (o.b) f += '';
if (o.i) f += '';
if (o.u) f += '';
f += '';
f += '';
f += '';
f += '';
var fontId = this.fonts.indexOf(f);
if (fontId < 0) { this.fonts.push(f); fontId = this.fonts.length - 1; }
var fillId = 0;
if (o.fill) {
var fl = '';
fillId = this.fills.indexOf(fl);
if (fillId < 0) { this.fills.push(fl); fillId = this.fills.length - 1; }
}
var borderId = 0;
if (o.bd) {
var bd = ''
+ (o.bd.l ? '' : '')
+ ''
+ (o.bd.t ? '' : '')
+ (o.bd.b ? '' : '')
+ '';
borderId = this.borders.indexOf(bd);
if (borderId < 0) { this.borders.push(bd); borderId = this.borders.length - 1; }
}
var align = '';
var xf = '' + align + '';
var id = this.xfs.indexOf(xf);
if (id < 0) { this.xfs.push(xf); id = this.xfs.length - 1; }
this.cache[key] = id;
return id;
};
XlStyles.prototype.xml = function () {
return ''
+ ''
+ '' + this.fonts.join('') + ''
+ '' + this.fills.join('') + ''
+ '' + this.borders.join('') + ''
+ ''
+ '' + this.xfs.join('') + ''
+ ''
+ '';
};
function colLetter(n) { var s = ''; while (n > 0) { var m = (n - 1) % 26; s = String.fromCharCode(65 + m) + s; n = Math.floor((n - 1) / 26); } return s; }
function textUnits(s) {
var u = 0, str = String(s == null ? '' : s);
for (var i = 0; i < str.length; i++) {
var code = str.charCodeAt(i);
u += (code > 0x2000) ? 1 : 0.55;
}
return u;
}
/** 估算行高(磅) */
function estRowH(text, widthPx, fontPt, extraPt) {
var s = String(text == null ? '' : text);
var fontPx = fontPt / 0.75, lines = 0;
var segs = s.split('\n');
for (var i = 0; i < segs.length; i++) {
lines += Math.max(1, Math.ceil(textUnits(segs[i]) * fontPx / Math.max(40, widthPx - 15)));
}
if (!s) lines = 1;
return Math.round((lines * fontPt * 1.6 + (extraPt == null ? 8 : extraPt)) * 10) / 10;
}
function richRun(text, o) {
o = o || {};
return '' + (o.b ? '' : '') + ''
+ ''
+ '' + xmlEsc(text) + '';
}
function buildXlsxBytes() {
var c = tpl();
var fnt = officeFont(c);
var P = mainPalette(c), S = sidePalette(c);
var st = new XlStyles();
var img = parseDataUrl(state.basic.avatar);
// 列宽(字符宽)→ 像素 ≈ 7*chars+5
var COLS = [
{ w: 1.6 }, // A 页边
{ w: 2.2 }, // B 侧栏左内边距
{ w: 34 }, // C 侧栏内容
{ w: 2.2 }, // D 侧栏右内边距
{ w: 2.6 }, // E 栏间距
{ w: 61 }, // F 主区内容
{ w: 1.6 } // G 页边
];
var PX = COLS.map(function (x) { return Math.round(x.w * 7 + 5); });
var SIDE_PX = PX[2], MAIN_PX = PX[5];
var FULL_PX = PX[1] + PX[2] + PX[3] + PX[4] + PX[5];
var rows = [], merges = [], drawAnchor = null;
function emitRow(cells, h) {
rows.push({ cells: cells, h: h });
return rows.length; // 1-based
}
/** 整幅带状行(A..G 同底色,内容合并 B:F) */
function bandRow(text, sOpt, h, fillAll, rich) {
var sid = st.get(sOpt);
var padId = st.get({ fill: fillAll, sz: 8, name: fnt });
var cells = [{ col: 1, s: padId }];
cells.push(rich ? { col: 2, s: sid, rich: rich } : { col: 2, s: sid, v: text });
for (var i = 3; i <= 6; i++) cells.push({ col: i, s: sid });
cells.push({ col: 7, s: padId });
var r = emitRow(cells, h);
merges.push(colLetter(2) + r + ':' + colLetter(6) + r);
return r;
}
/** 双栏行:left/right 为 {v|rich, s, h} 或 null */
function dualRow(left, right, sideFill, mainFill) {
var sidePad = st.get({ fill: sideFill, sz: 8, name: fnt });
var mainPad = st.get({ fill: mainFill, sz: 8, name: fnt });
var lS = left ? st.get(left.s) : sidePad;
var rS = right ? st.get(right.s) : mainPad;
var cells = [
{ col: 1, s: mainPad },
{ col: 2, s: sidePad },
left && left.rich ? { col: 3, s: lS, rich: left.rich } : { col: 3, s: lS, v: left ? left.v : '' },
{ col: 4, s: sidePad },
{ col: 5, s: mainPad },
right && right.rich ? { col: 6, s: rS, rich: right.rich } : { col: 6, s: rS, v: right ? right.v : '' },
{ col: 7, s: mainPad }
];
var h = Math.max(left ? left.h : 0, right ? right.h : 0, 13);
return emitRow(cells, h);
}
/* ---------- 内容块生成(返回 {v|rich, s, h} 数组) ---------- */
function blkSecTitle(title, PL, widthPx) {
var bg = PL.bg, base = { name: fnt, sz: 13.5, b: true, color: PL.primary, fill: bg, v: 'bottom' };
if (c.secStyle === 'background') base = { name: fnt, sz: 11.5, b: true, color: bestOn(PL.primary), fill: PL.primary, v: 'center', h: 'left', indent: 1 };
else if (c.secStyle === 'underline') base.bd = { b: { s: 'medium', c: PL.accent } };
else if (c.secStyle === 'left-border') { base.bd = { l: { s: 'thick', c: PL.accent } }; base.indent = 1; }
else if (c.secStyle === 'center-line') { base.h = 'center'; base.bd = { b: { s: 'thin', c: fadeHex(PL.primary, bg, 0.4) } }; }
return [{ v: title, s: base, h: c.secStyle === 'background' ? 24 : 23 }];
}
function blkText(text, PL, widthPx, o) {
o = o || {};
var sz = o.sz || 9.5;
return [{
v: text,
s: { name: fnt, sz: sz, b: !!o.b, color: o.color || PL.body, fill: o.fill || PL.bg, h: o.h || 'left', v: 'top', indent: o.indent, bd: o.bd },
h: estRowH(text, widthPx, sz, o.pad == null ? 6 : o.pad)
}];
}
function blkGap(PL, h) { return [{ v: '', s: { fill: PL.bg, sz: 8, name: fnt }, h: h || 6 }]; }
function blkItem(item, isEdu, PL, widthPx) {
var title = isEdu ? (item.school || '') : (item.company || item.name || '');
var subtitle = isEdu ? ((item.major || '') + (item.degree ? ' · ' + item.degree : '')) : (item.title || item.role || '');
var card = c.itemStyle === 'card', dotline = c.itemStyle === 'dot-line';
var fill = card ? PL.card : PL.bg;
var bd = card ? { l: { s: 'thick', c: PL.accent } } : (dotline ? { l: { s: 'medium', c: PL.line } } : null);
var ind = (card || dotline) ? 1 : 0;
var out = [];
// 标题行(富文本:左标题 + 右日期)
out.push({
rich: richRun(title, { b: true, sz: 11.5, color: PL.primary, name: fnt }),
s: { name: fnt, sz: 11.5, b: true, color: PL.primary, fill: fill, bd: bd, indent: ind, v: 'bottom' },
h: estRowH(title, widthPx - 30, 11.5, 5)
});
if (item.date) {
out.push({
v: item.date,
s: { name: 'Consolas', sz: 9, b: true, color: PL.muted, fill: fill, bd: bd, indent: ind, h: 'right' },
h: 13.5
});
}
if (subtitle) {
out.push({
v: subtitle,
s: { name: fnt, sz: 10.5, b: true, color: PL.soft, fill: fill, bd: bd, indent: ind },
h: estRowH(subtitle, widthPx, 10.5, 5)
});
}
if (item.desc) {
out.push({
v: item.desc,
s: { name: fnt, sz: 9.5, color: PL.body, fill: fill, bd: bd, indent: ind },
h: estRowH(item.desc, widthPx, 9.5, 8)
});
}
out.push({ v: '', s: { fill: PL.bg, sz: 8, name: fnt }, h: 8 });
return out;
}
function blkSkills(PL, widthPx) {
if (!state.skills.length) return [];
var out = blkSecTitle('核心专业矩阵', PL, widthPx);
var stl = c.skillStyle;
if (stl === 'tag') {
var line = state.skills.map(function (s) { return '· ' + (s.name || ''); }).join('\n');
out.push({ v: line, s: { name: fnt, sz: 9.5, b: true, color: PL.primary, fill: PL.tag, indent: 1 }, h: estRowH(line, widthPx, 9.5, 10) });
} else if (stl === 'text') {
var line2 = state.skills.map(function (s) { return s.name || ''; }).join(' · ');
out.push({ v: line2, s: { name: fnt, sz: 10, b: true, u: true, color: PL.primary, fill: PL.bg }, h: estRowH(line2, widthPx, 10, 8) });
} else if (stl === 'dot') {
state.skills.forEach(function (sk) {
var lv = Math.max(1, Math.min(5, parseInt(sk.level) || 3));
out.push({
rich: richRun((sk.name || '') + ' ', { b: true, sz: 10, color: PL.primary, name: fnt })
+ richRun(new Array(lv + 1).join('●'), { sz: 10, color: PL.accent, name: fnt })
+ richRun(new Array(5 - lv + 1).join('●'), { sz: 10, color: PL.track, name: fnt }),
s: { name: fnt, sz: 10, color: PL.primary, fill: PL.bg },
h: estRowH((sk.name || '') + ' ●●●●●', widthPx, 10, 5)
});
});
} else { // bar
state.skills.forEach(function (sk) {
var lv = Math.max(1, Math.min(5, parseInt(sk.level) || 3));
var total = Math.max(10, Math.floor(widthPx / 12));
var full = Math.round(total * lv / 5);
out.push({ v: sk.name || '', s: { name: fnt, sz: 9.5, b: true, color: PL.primary, fill: PL.bg }, h: estRowH(sk.name, widthPx, 9.5, 3) });
out.push({
rich: richRun(new Array(full + 1).join('█'), { sz: 7, color: PL.accent, name: 'Consolas' })
+ richRun(new Array(total - full + 1).join('█'), { sz: 7, color: PL.track, name: 'Consolas' }),
s: { name: 'Consolas', sz: 7, color: PL.accent, fill: PL.bg },
h: 9
});
out.push({ v: '', s: { fill: PL.bg, sz: 6, name: fnt }, h: 4 });
});
}
out.push({ v: '', s: { fill: PL.bg, sz: 8, name: fnt }, h: 8 });
return out;
}
function blkMain(widthPx, opt) {
opt = opt || {};
var out = [];
if (state.summary) {
out = out.concat(blkSecTitle('高管执行摘要', P, widthPx));
out = out.concat(blkText(state.summary, P, widthPx, { sz: 10, color: P.soft, pad: 10, bd: { l: { s: 'thick', c: fadeHex(P.accent, P.bg, 0.2) } }, indent: 1 }));
out = out.concat(blkGap(P, 8));
}
if (state.work.length) {
out = out.concat(blkSecTitle('高管核心履历', P, widthPx));
state.work.forEach(function (x) { out = out.concat(blkItem(x, false, P, widthPx)); });
out = out.concat(blkGap(P, 6));
}
if (state.projects.length) {
out = out.concat(blkSecTitle('重大战略与资本项目', P, widthPx));
state.projects.forEach(function (x) { out = out.concat(blkItem(x, false, P, widthPx)); });
out = out.concat(blkGap(P, 6));
}
if (!opt.skipEdu && state.education.length) {
out = out.concat(blkSecTitle('教育背景与资质', P, widthPx));
state.education.forEach(function (x) { out = out.concat(blkItem(x, true, P, widthPx)); });
out = out.concat(blkGap(P, 6));
}
if (opt.withSkills) out = out.concat(blkSkills(P, widthPx));
return out;
}
var twoCol = (c.layout === 'side' || c.layout === 'magazine');
/* ================= 头部带状区(top / hero / split / magazine / classic / stripe / frame) ================= */
function pushHeaderBand() {
if (c.layout === 'top' || c.layout === 'hero') {
var B = bandPalette(c.topBg, c.topText, c.accent);
var centerH = (c.layout === 'hero');
bandRow('', { fill: B.bg, sz: 8, name: fnt }, 10, B.bg);
if (img) { drawAnchor = { row: rows.length, col: centerH ? 3 : 5, size: 92 }; bandRow('', { fill: B.bg, sz: 8, name: fnt }, 74, B.bg); }
bandRow(state.basic.name || '', { name: fnt, sz: 25, b: true, color: B.text, fill: B.bg, h: centerH ? 'center' : 'left', v: 'bottom' }, 36, B.bg);
bandRow(state.basic.title || '', { name: fnt, sz: 12.5, b: true, color: B.accent, fill: B.bg, h: centerH ? 'center' : 'left' }, estRowH(state.basic.title, FULL_PX, 12.5, 6), B.bg);
var ct = getContacts().join(' ');
if (ct) bandRow(ct, { name: fnt, sz: 9.5, color: B.body, fill: B.bg, h: centerH ? 'center' : 'left' }, estRowH(ct, FULL_PX, 9.5, 10), B.bg);
bandRow('', { fill: B.bg, sz: 8, name: fnt }, 12, B.bg);
if (c.topBorderBottom) {
var bc = /#[0-9a-fA-F]{3,6}/.test(c.topBorderBottom) ? hex6(c.topBorderBottom.match(/#[0-9a-fA-F]{3,6}/)[0]) : B.accent;
bandRow('', { fill: bc, sz: 6, name: fnt }, 5, bc);
} else {
bandRow('', { fill: B.line, sz: 6, name: fnt }, 3, B.line);
}
bandRow('', { fill: P.bg, sz: 8, name: fnt }, 14, P.bg);
} else if (c.layout === 'split') {
var LP = bandPalette(c.splitLeftBg || c.primary, c.splitLeftText || '#ffffff', c.accent);
var RP = bandPalette(c.splitRightBg || '#f1f5f9', c.splitRightText || c.text, c.accent);
var lPad = st.get({ fill: LP.bg, sz: 8, name: fnt });
var rPad = st.get({ fill: RP.bg, sz: 8, name: fnt });
function splitRow(lv, ls, lh, rv, rs, rh) {
var lS = st.get(ls || { fill: LP.bg, sz: 8, name: fnt });
var rS = st.get(rs || { fill: RP.bg, sz: 8, name: fnt });
var cells = [
{ col: 1, s: lPad }, { col: 2, s: lPad }, { col: 3, s: lS, v: lv },
{ col: 4, s: rPad }, { col: 5, s: rPad }, { col: 6, s: rS, v: rv }, { col: 7, s: rPad }
];
return emitRow(cells, Math.max(lh || 0, rh || 0, 13));
}
splitRow('', null, 12, '', null, 12);
if (img) { drawAnchor = { row: rows.length, col: 6, size: 76 }; }
var cts = getContacts();
splitRow(state.basic.name || '', { name: fnt, sz: 23, b: true, color: LP.text, fill: LP.bg, v: 'bottom', indent: 1 }, 34,
cts[0] || '', { name: fnt, sz: 10, b: true, color: RP.body, fill: RP.bg, indent: img ? 6 : 1 }, 20);
splitRow(state.basic.title || '', { name: fnt, sz: 11.5, b: true, color: LP.accent, fill: LP.bg, indent: 1 }, estRowH(state.basic.title, SIDE_PX, 11.5, 6),
cts[1] || '', { name: fnt, sz: 10, b: true, color: RP.body, fill: RP.bg, indent: img ? 6 : 1 }, 20);
splitRow('', null, 8, cts[2] || '', { name: fnt, sz: 10, b: true, color: RP.body, fill: RP.bg, indent: img ? 6 : 1 }, 20);
splitRow('', null, 8, cts[3] || '', { name: fnt, sz: 10, b: true, color: RP.body, fill: RP.bg, indent: img ? 6 : 1 }, 20);
splitRow('', null, 12, '', null, 12);
bandRow('', { fill: P.bg, sz: 8, name: fnt }, 16, P.bg);
} else if (c.layout === 'magazine') {
var MB = bandPalette(c.topBg, c.topText, c.accent);
bandRow('', { fill: MB.bg, sz: 8, name: fnt }, 10, MB.bg);
if (img) { drawAnchor = { row: rows.length, col: 6, size: 66 }; }
bandRow(state.basic.name || '', { name: fnt, sz: 21, b: true, color: MB.text, fill: MB.bg, v: 'bottom' }, 31, MB.bg);
bandRow(state.basic.title || '', { name: fnt, sz: 11.5, b: true, color: MB.accent, fill: MB.bg }, estRowH(state.basic.title, FULL_PX, 11.5, 6), MB.bg);
bandRow('', { fill: MB.bg, sz: 8, name: fnt }, 10, MB.bg);
bandRow('', { fill: MB.line, sz: 6, name: fnt }, 3, MB.line);
} else if (c.layout === 'classic' || c.layout === 'stripe' || c.layout === 'frame') {
var centerC = (c.headerAlign === 'center' || c.layout === 'frame');
bandRow('', { fill: P.bg, sz: 8, name: fnt }, 12, P.bg);
if (img) { drawAnchor = { row: rows.length, col: centerC ? 3 : 6, size: 84 }; bandRow('', { fill: P.bg, sz: 8, name: fnt }, 66, P.bg); }
bandRow(state.basic.name || '', { name: fnt, sz: 24, b: true, color: P.primary, fill: P.bg, h: centerC ? 'center' : 'left', v: 'bottom' }, 34, P.bg);
bandRow(state.basic.title || '', { name: fnt, sz: 12, b: true, color: P.accent, fill: P.bg, h: centerC ? 'center' : 'left' }, estRowH(state.basic.title, FULL_PX, 12, 6), P.bg);
var ct2 = getContacts().join(' ');
if (ct2) bandRow(ct2, { name: fnt, sz: 9.5, color: P.muted, fill: P.bg, h: centerC ? 'center' : 'left' }, estRowH(ct2, FULL_PX, 9.5, 8), P.bg);
var hbC = P.line, hbOn = true;
if (c.layout === 'classic') {
if (!c.headerBorder || c.headerBorder === 'none') { hbC = P.line; }
else if (/#[0-9a-fA-F]{3,6}/.test(c.headerBorder)) hbC = hex6(c.headerBorder.match(/#[0-9a-fA-F]{3,6}/)[0]);
} else if (c.layout === 'frame') {
hbC = /#[0-9a-fA-F]{3,6}/.test(c.frameBorder || '') ? hex6(c.frameBorder.match(/#[0-9a-fA-F]{3,6}/)[0]) : P.primary;
} else { hbC = P.accent; }
if (hbOn) bandRow('', { fill: P.bg, sz: 6, name: fnt, bd: { b: { s: 'medium', c: hbC } } }, 8, P.bg);
bandRow('', { fill: P.bg, sz: 8, name: fnt }, 14, P.bg);
}
}
pushHeaderBand();
if (twoCol) {
/* ---- 双栏:侧栏(C 列) + 主区(F 列) ---- */
var leftBlocks = [];
if (c.layout === 'side') {
if (img) { drawAnchor = { row: rows.length, col: 3, size: 96 }; leftBlocks.push({ v: '', s: { fill: S.bg, sz: 8, name: fnt }, h: 78 }); }
if (c.nameInSide) {
leftBlocks.push({ v: state.basic.name || '', s: { name: fnt, sz: 20, b: true, color: S.text, fill: S.bg, h: 'center', v: 'bottom' }, h: 30 });
leftBlocks.push({ v: state.basic.title || '', s: { name: fnt, sz: 10, b: true, color: S.accent, fill: S.bg, h: 'center' }, h: estRowH(state.basic.title, SIDE_PX, 10, 6) });
}
leftBlocks.push({ v: '', s: { fill: S.bg, sz: 8, name: fnt, bd: { b: { s: 'thin', c: S.line } } }, h: 12 });
}
getContacts().forEach(function (x) {
leftBlocks.push({ v: x, s: { name: fnt, sz: 9.5, color: S.body, fill: S.bg }, h: estRowH(x, SIDE_PX, 9.5, 6) });
});
leftBlocks.push({ v: '', s: { fill: S.bg, sz: 8, name: fnt, bd: { b: { s: 'thin', c: S.line } } }, h: 10 });
leftBlocks.push({ v: '', s: { fill: S.bg, sz: 8, name: fnt }, h: 10 });
leftBlocks = leftBlocks.concat(blkSkills(S, SIDE_PX));
var rightBlocks = [];
if (c.layout === 'side' && !c.nameInSide) {
rightBlocks.push({ v: state.basic.name || '', s: { name: fnt, sz: 24, b: true, color: P.primary, fill: P.bg, v: 'bottom' }, h: 34 });
rightBlocks.push({ v: state.basic.title || '', s: { name: fnt, sz: 12, b: true, color: P.accent, fill: P.bg, bd: { b: { s: 'medium', c: P.accent } } }, h: estRowH(state.basic.title, MAIN_PX, 12, 8) });
rightBlocks.push({ v: '', s: { fill: P.bg, sz: 8, name: fnt }, h: 12 });
}
rightBlocks = rightBlocks.concat(blkMain(MAIN_PX, {}));
var n = Math.max(leftBlocks.length, rightBlocks.length);
for (var i = 0; i < n; i++) dualRow(leftBlocks[i] || null, rightBlocks[i] || null, S.bg, P.bg);
} else {
/* ---- 单栏 ---- */
var blocks = blkMain(FULL_PX, { skipEdu: false, withSkills: true });
blocks.forEach(function (b) {
var sid = st.get(b.s);
var padId = st.get({ fill: b.s.fill || P.bg, sz: 8, name: fnt });
var cells = [{ col: 1, s: st.get({ fill: P.bg, sz: 8, name: fnt }) }];
cells.push(b.rich ? { col: 2, s: sid, rich: b.rich } : { col: 2, s: sid, v: b.v });
for (var k = 3; k <= 6; k++) cells.push({ col: k, s: sid });
cells.push({ col: 7, s: st.get({ fill: P.bg, sz: 8, name: fnt }) });
var r = emitRow(cells, b.h);
merges.push(colLetter(2) + r + ':' + colLetter(6) + r);
});
}
// 尾部留白,保证底色铺满
for (var t2 = 0; t2 < 3; t2++) {
var padS = st.get({ fill: P.bg, sz: 8, name: fnt });
var cs = [];
for (var q = 1; q <= 7; q++) cs.push({ col: q, s: padS });
emitRow(cs, 14);
}
/* ---------- 序列化 ---------- */
var colsXml = '' + COLS.map(function (x, i) {
return '';
}).join('') + '';
var sheetData = '' + rows.map(function (r, i) {
var rn = i + 1;
var cells = r.cells.slice().sort(function (a, b) { return a.col - b.col; }).map(function (cl) {
var ref = colLetter(cl.col) + rn;
if (cl.rich) return '' + cl.rich + '';
if (cl.v == null || cl.v === '') return '';
return '' + xmlEsc(cl.v) + '';
}).join('');
return '' + cells + '
';
}).join('') + '';
var mergeXml = merges.length ? '' + merges.map(function (m) { return ''; }).join('') + '' : '';
var sheetXml = ''
+ ''
+ ''
+ ''
+ ''
+ ''
+ colsXml + sheetData + mergeXml
+ ''
+ ''
+ (img ? '' : '')
+ '';
var workbookXml = ''
+ ''
+ '';
var wbRels = ''
+ ''
+ ''
+ ''
+ '';
var rootRels2 = ''
+ ''
+ ''
+ '';
var ct2x = ''
+ ''
+ ''
+ ''
+ ''
+ ''
+ ''
+ ''
+ ''
+ ''
+ ''
+ (img ? '' : '')
+ '';
var files2 = [
{ name: '[Content_Types].xml', data: ct2x },
{ name: '_rels/.rels', data: rootRels2 },
{ name: 'xl/workbook.xml', data: workbookXml },
{ name: 'xl/_rels/workbook.xml.rels', data: wbRels },
{ name: 'xl/styles.xml', data: st.xml() },
{ name: 'xl/worksheets/sheet1.xml', data: sheetXml }
];
if (img) {
var an = drawAnchor || { row: 1, col: 3, size: 90 };
var emu = Math.round(an.size * 9525);
var drawing = ''
+ ''
+ ''
+ '' + (an.col - 1) + '190500' + an.row + '19050'
+ ''
+ ''
+ ''
+ ''
+ '';
files2.push({ name: 'xl/drawings/drawing1.xml', data: drawing });
files2.push({
name: 'xl/drawings/_rels/drawing1.xml.rels',
data: ''
+ ''
+ ''
+ ''
});
files2.push({
name: 'xl/worksheets/_rels/sheet1.xml.rels',
data: ''
+ ''
+ ''
+ ''
});
files2.push({ name: 'xl/media/image1.' + img.ext, data: img.bytes });
}
return makeZip(files2);
}
/* ==================================================================================
★★★ 原有能力完整保留:html2canvas 像素级快照导出(图片快照模式) ★★★
================================================================================== */
async function getPerfectCanvasImage() {
const mask = document.getElementById('loadingMask');
mask.classList.add('active');
try {
// 1. 获取原件并深度克隆
const originalEl = document.getElementById('previewPage').firstElementChild;
const clone = originalEl.cloneNode(true);
// 2. 将克隆体强行挂载到 Body 最外层,无视任何父级限制
clone.style.position = 'absolute';
clone.style.top = '-9999px';
clone.style.left = '0';
clone.style.transform = 'none';
clone.style.width = '794px';
clone.style.height = 'auto';
clone.style.boxShadow = 'none';
document.body.appendChild(clone);
// 3. 等待字体/图片渲染
await new Promise(r => setTimeout(r, 300));
// 4. 完整截取自由伸展的克隆体
const canvas = await html2canvas(clone, {
scale: 2, useCORS: true, backgroundColor: '#ffffff', allowTaint: true, logging: false
});
document.body.removeChild(clone);
return canvas.toDataURL('image/jpeg', 0.95);
} finally {
mask.classList.remove('active');
}
}
async function exportWordImage() {
const imgData = await getPerfectCanvasImage();
const html = `
BlueCV Pro - Resume
`;
const blob = new Blob(['\ufeff', html], { type: 'application/msword;charset=utf-8' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a'); a.href = url;
a.download = safeName() + '_BlueCV_Pro.doc';
document.body.appendChild(a); a.click(); document.body.removeChild(a);
setTimeout(() => URL.revokeObjectURL(url), 1500);
}
async function exportExcelImage() {
const imgData = await getPerfectCanvasImage();
const html = `
`;
const blob = new Blob(['\ufeff', html], { type: 'application/vnd.ms-excel;charset=utf-8' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a'); a.href = url;
a.download = safeName() + '_BlueCV_Pro.xls';
document.body.appendChild(a); a.click(); document.body.removeChild(a);
setTimeout(() => URL.revokeObjectURL(url), 1500);
}
/* ==================================================================================
★★★ 导出统一入口(原生 OOXML 优先,异常时自动回退图片快照) ★★★
================================================================================== */
function withMask(text, fn) {
var mask = document.getElementById('loadingMask');
document.getElementById('loadingText').textContent = text;
mask.classList.add('active');
return new Promise(function (resolve, reject) {
setTimeout(function () {
try { var r = fn(); mask.classList.remove('active'); resolve(r); }
catch (err) { mask.classList.remove('active'); reject(err); }
}, 60);
});
}
async function exportWord() {
if (exportMode === 'image') {
try { await exportWordImage(); toast('已导出 Word 图片快照版 (.doc)'); }
catch (e) { console.error(e); alert('导出 Word 失败,请重试'); }
return;
}
try {
const bytes = await withMask('正在生成可编辑 Word 文档 (.docx)...', buildDocxBytes);
saveBytes(bytes, safeName() + '_BlueCV_Pro.docx',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document');
toast('✅ 已导出 Word(.docx,版式/配色/内容与预览一致,可直接编辑)');
} catch (e) {
console.error('[docx]', e);
try { await exportWordImage(); toast('原生导出异常,已回退为图片快照版 Word'); }
catch (e2) { alert('导出 Word 失败:' + (e && e.message ? e.message : e)); }
}
}
async function exportExcel() {
if (exportMode === 'image') {
try { await exportExcelImage(); toast('已导出 Excel 图片快照版 (.xls)'); }
catch (e) { console.error(e); alert('导出 Excel 失败,请重试'); }
return;
}
try {
const bytes = await withMask('正在生成可编辑 Excel 工作簿 (.xlsx)...', buildXlsxBytes);
saveBytes(bytes, safeName() + '_BlueCV_Pro.xlsx',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
toast('✅ 已导出 Excel(.xlsx,配色/版面/内容与预览一致,单元格可编辑)');
} catch (e) {
console.error('[xlsx]', e);
try { await exportExcelImage(); toast('原生导出异常,已回退为图片快照版 Excel'); }
catch (e2) { alert('导出 Excel 失败:' + (e && e.message ? e.message : e)); }
}
}
// 启动
init();